When I read about the concept of lift, it's implemented like this (in Javascript)
const liftA2 = f => (a, b) => b.ap(a.map(f));
I realise there is a case in which liftA2 will produce an error: when b is a Right/Just and a is a Left/Nothing, because mapping a Left/Nothing will do nothing to the value when we would need it to become a partially applied function.
When a and b are both a Left it doesn't blow up but of course the value of the returned Left is going to be the value of b which I suppose could be a problem depending on what you expect.
Is it a thing to lift a function to be used with these types? Should I systematically guard against these cases explicitly before using such a function? Is the above implementation correct/complete?
You will find more details about the issue bellow
Let us define the function to liftconst add = a => b => a + b;In the case of a basic
Wrapperimplementingof,apandmap, we can follow what is happeningclass Wrapper { constructor(value) { this.value = value; } static of(value) { return new Wrapper(value); } map(f) { return Wrapper.of(f(this.value)); } ap(a) { return this.map(a.value); } } const a = Wrapper.of(1); const b = Wrapper.of(2); // liftA2 const tmp = a.map(add); // Wrapper { λ } b.ap(tmp); // Wrapper { 3 }But the thing with
EitherorMaybeis that they have aLeft/Nothingcase wheremapandapare intended to do nothing specialclass Left { constructor(value) { this.value = value; } static of(value) { return new Left(value); } map(f) { return this; } ap(a) { return this; } } class Right{ constructor(value) { this.value = value; } static of(value) { return new Right(value); } map(f) { return Right.of(f(this.value)); } ap(a) { return this.map(a.value); } } const a = Left.of(1); const b = Right.of(2); // liftA2 const tmp = a.map(add); // Left { 1 } b.ap(tmp); // Error because tmp's value is not a function
My Javascript is a little basic (I don't use it much). But I think, as has been pointed out in the comments, your implementation of
apis not doing what you want it to.First, take a look at this answer to a similar question about what lifting is in the first place. It is supposed to take a function of
nparameters and put it into the context of a given Functor/Monad where each parameter is contained in that Functor/Monad.In other words, if
f: 'a -> 'b -> 'c(a function that takes two parameters of types'aand'band returns a result of type'c) then we could use alift2where:which would turn
finto a function that takes anM[a]and anM[b]and returns anM[c](whereMin this case is yourEitherorMaybe).Since I am more familiar with F# I will use that as an example here. If I was to implement
lift2forOptionin F# (equivalent toMaybe) I would do it something like this:You see here that I match on both input types. Both have to be a
Someto return aSomevalue. Otherwise I just return aNone. In the case of aResult(equivalent to anEither) I would have to determine which way to bias the match. It could go either way in terms of whichLeft/Errorvalue to return.Using the above code in FSI I get the following: