admin管理员组文章数量:1401362
I have a need for a variadic version of R.either
. After doing some searching around the web, I have not found a solution. R.anyPass
would work but it returns a Boolean instead of the original value. Is there already a solution that I have overlooked? If not, what would be the most optimal way to write a variadic either utility function?
An example:
const test = variadicEither(R.multiply(0), R.add(-1), R.add(1), R.add(2))
test(1) // => 2
I have a need for a variadic version of R.either
. After doing some searching around the web, I have not found a solution. R.anyPass
would work but it returns a Boolean instead of the original value. Is there already a solution that I have overlooked? If not, what would be the most optimal way to write a variadic either utility function?
An example:
const test = variadicEither(R.multiply(0), R.add(-1), R.add(1), R.add(2))
test(1) // => 2
Share
Improve this question
edited Jul 5, 2019 at 18:07
webstermath
asked Jul 1, 2019 at 18:14
webstermathwebstermath
5991 gold badge7 silver badges15 bronze badges
7
-
2
It's not quite clear what output you want if there is no match. but would this do:
const varEither = (...fns) => (x, res = null, fn = fns.find(fn => res = fn(x))) => res
? It should run each function once until it hits a truthy result, returning that, or return the result of the last function. Is that what you want? – Scott Sauyet Commented Jul 1, 2019 at 19:55 - The above does satisfy all requirements. I suggest submitting it as an answer so it can be considered for the best implementation. – webstermath Commented Jul 2, 2019 at 16:03
- added it. But I personally prefer the updated answer from custommander, at least if you're already using Ramda in your code. – Scott Sauyet Commented Jul 2, 2019 at 16:13
- As a side concern, which name would be most appropriate for a variadic or array accepting either? Some that e to mind: eitherV, varEither, eitherAny, anyEither. Thoughts? – webstermath Commented Jul 3, 2019 at 18:52
- 2 You know what they say, right: The two hardest problems in software are cache invalidation, naming things, and off-by-one errors. – Scott Sauyet Commented Jul 3, 2019 at 19:50
5 Answers
Reset to default 4You could use a bination of reduce
+ reduced
:
const z = (...fns) => x => reduce((res, fn) => res ? reduced(res) : fn(x), false, fns);
console.log(
z(always(0), always(10), always(2))(11),
z(always(0), always(''), always(15), always(2))(11),
)
<script src="https://cdnjs.cloudflare./ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {reduce, reduced, always} = R;</script>
(previous attempt)
I would do something like this:
const z = unapply(curry((fns, x) => find(applyTo(x), fns)(x)));
console.log(
z(always(0), always(15), always(2))(10),
z(always(0), always(''), always(NaN), always(30), always(2))(10),
);
<script src="https://cdnjs.cloudflare./ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {unapply, curry, find, applyTo, always} = R;</script>
There are three main caveats to this though!
- You have to call
z
in two "passes", i.e.z(...functions)(x)
- Although it should be easy to add, I didn't care about the case where no function "matches"
- Perhaps not a big deal but worth noting: a matching predicate will be executed twice
Without Ramda ...
I'd probably write this using simple recursion -
const always = x =>
_ => x
const identity = x =>
x
const veither = (f = identity, ...more) => (...args) =>
more.length === 0
? f (...args)
: f (...args) || veither (...more) (...args)
const test =
veither
( always (0)
, always (false)
, always ("")
, always (1)
, always (true)
)
console .log (test ())
// 1
But there's more to it ...
R.either
has to be one the more eccentric functions in the Ramda library. If you read the documentation closely, R.either
has two (2) behaviour variants: it can return -
a function that that passes its argument to each of the two functions,
f
andg
, and returns the first truthy value -g
will not be evaluated iff
's result is truthy.Or, an applicative functor
The signature for R.either
says -
either : (*… → Boolean) → (*… → Boolean) → (*… → Boolean)
But that's definitely fudging it a bit. For our two cases above, the following two signatures are much closer -
// variant 1
either : (*… → a) → (*… → b) → (*… → a|b)
// variant 2
either : Apply f => f a → f b → f (a|b)
Let's confirm these two variants with simple tests -
const { always, either } =
R
const { Just, Nothing } =
folktale.maybe
// variant 1 returns a function
const test =
either
( always (0)
, always (true)
)
console.log(test()) // => true
// variant 2 returns an applicative functor
const result =
either
( Just (false)
, Just (1)
)
console.log(result) // => Just { 1 }
<script src="https://cdnjs.cloudflare./ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/folktale/2.0.1/folktale.min.js"></script>
Double down ...
Now let's make a super-powered veither
that offers the same dual capability as R.either
-
const vor = (a, ...more) =>
a || vor (...more)
const veither = (f, ...more) =>
f instanceof Function
// variant 1
? (...args) =>
f (...args) || veither (...more) (...args)
// variant 2
: liftN (more.length + 1, vor) (f, ...more)
It works just like R.either
except now it accepts two or more arguments. Behaviour of each variant is upheld -
// variant 1 returns a function
const test =
veither
( always (false)
, always (0)
, always ("fn")
, always (2)
)
test () // => "fn"
// variant 2 returns an applicative functor
veither
( Just (0)
, Just (false)
, Just ("")
, Just ("ap")
, Just (2)
)
// => Just { "ap" }
You can view the source for R.either
and pare it with veither
above. Uncurried and restyled, you can see its many similarities here -
// either : (*… → a) → (*… → b) → (*… → a|b)
// either : Apply f => f a -> f b -> f (a|b)
const either = (f, g) =>
isFunction (f)
// variant 1
? (...args) =>
f (...args) || g (...args)
// variant 2
: lift (or) (f, g)
Expand the snippet below to verify the results in your own browser -
const { always, either, liftN } =
R
const { Just, Nothing } =
folktale.maybe
const vor = (a, ...more) =>
a || vor (...more)
const veither = (f, ...more) =>
f instanceof Function
? (...args) =>
f (...args) || veither (...more) (...args)
: liftN (more.length + 1, vor) (f, ...more)
// variant 1 returns a function
const test =
veither
( always (false)
, always (0)
, always ("fn")
, always (2)
)
console .log (test ()) // "fn"
// variant 2 returns an applicative functor
const result =
veither
( Just (0)
, Just (false)
, Just ("")
, Just ("ap")
, Just (2)
)
console .log (result) // Just { "ap" }
<script src="https://cdnjs.cloudflare./ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/folktale/2.0.1/folktale.min.js"></script>
With hindsight and foresight ...
With one little trick, we can skip all the ceremony of reasoning about our own veither
. In this implementation, we simply make a recurring call to R.either
-
const veither = (f, ...more) =>
more.length === 0
? R.either (f, f) // ^_^
: R.either (f, veither (...more))
I show you this because it works nicely and preserves the behaviour of both variants, but it should be avoided because it builds a much more plex tree of putations. Nevertheless, expand the snippet below to verify it works -
const { always, either } =
R
const { Just, Nothing } =
folktale.maybe
const veither = (f, ...more) =>
more.length === 0
? either (f, f)
: either (f, veither (...more))
// variant 1 returns a function
const test =
veither
( always (false)
, always (0)
, always ("fn")
, always (2)
)
console .log (test ()) // "fn"
// variant 2 returns an applicative functor
const result =
veither
( Just (0)
, Just (false)
, Just ("")
, Just ("ap")
, Just (2)
)
console .log (result) // Just { "ap" }
<script src="https://cdnjs.cloudflare./ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/folktale/2.0.1/folktale.min.js"></script>
Update:
I personally prefer the previous version, it is much more cleaner, but you could eventually ramdify it even more (you cannot write it entirely point-free due to the recursion):
const either = (...fns) => R.converge(R.either, [
R.head,
R.pipe(
R.tail,
R.ifElse(R.isEmpty, R.identity, R.apply(either)),
),
])(fns);
const result = either(R.always(null), R.always(0), R.always('truthy'))();
console.log(`result is "${result}"`);
<script src="https://cdnjs.cloudflare./ajax/libs/ramda/0.26.1/ramda.js"></script>
Update:
as per @custommander's suggestion, recursion may be nested in the right branch to have a much cleaner script...
const either = (...fns) => (...values) => {
const [left = R.identity, ...rest] = fns;
return R.either(
left,
rest.length ? either(...rest) : R.identity,
)(...values);
}
const result = either(R.always(null), R.always(0), R.always('truthy'))();
console.log(`result is "${result}"`);
<script src="https://cdnjs.cloudflare./ajax/libs/ramda/0.26.1/ramda.js"></script>
You could possibly call R.either
recursively...
const either = (...fns) => (...values) => {
const [left = R.identity, right = R.identity, ...rest] = fns;
return R.either(left, right)(...values) || (
rest.length ? either(...rest)(...values) : null
);
}
<script src="https://cdnjs.cloudflare./ajax/libs/ramda/0.26.1/ramda.js"></script>
The 1st function findTruthyFn
is used to find a truty function or take the last function if none of them return a truthy result.
The 2nd function fn
gets a list of functions, and the value, uses findTruthyFn
to find the function, and apply it to the value to get the result.
const { either, pipe, applyTo, flip, find, always, last, converge, call, identity } = R
const findTruthyFn = fns => either(
pipe(applyTo, flip(find)(fns)),
always(last(fns))
)
const fn = fns => converge(call, [findTruthyFn(fns), identity])
const check = fn([x => x + 1, x => x - 1])
console.log(check(1))
console.log(check(-1))
<script src="https://cdnjs.cloudflare./ajax/libs/ramda/0.26.1/ramda.js"></script>
If you want to limit the number of calls to of matching functions to one, you can memoize the functions before testing them:
const { either, pipe, applyTo, flip, find, always, last, converge, call, identity, map, memoizeWith } = R
const findTruthyFn = fns => either(
pipe(applyTo, flip(find)(map(memoizeWith(identity), fns))),
always(last(fns))
)
const fn = fns => converge(call, [findTruthyFn(fns), identity])
const check = fn([
x => console.log('1st called') || x + 1,
x => console.log('2nd called') || x - 1
])
console.log(check(1))
console.log(check(-1))
<script src="https://cdnjs.cloudflare./ajax/libs/ramda/0.26.1/ramda.js"></script>
This (non-Ramda) version is quite simple, and it seems to do what's needed:
const varEither = (...fns) => (x, res = null, fn = fns.find(fn => res = fn(x))) => res
If you need to supply multiple parameters to the resulting function, it wouldn't be much harder:
const varEither = (...fns) => (...xs) => {
let res = null;
fns .find (fn => res = fn (...xs) )
return res;
}
But I've got to say calling fns.find
for its side-effects does seem quite dirty, which might make me choose custommander's updated version instead of this.
本文标签: javascriptIs there a Variadic Version of either (Reither)Stack Overflow
版权声明:本文标题:javascript - Is there a Variadic Version of either (R.either)? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744293085a2599208.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论