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