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
and g
, and returns the first truthy value - g
will not be evaluated if f
'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.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script src="https://cdnjs.cloudflare.com/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 compare 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.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script src="https://cdnjs.cloudflare.com/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 complex tree of computations. 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.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/folktale/2.0.1/folktale.min.js"></script>