For instance, if we try to combine two composed applicatives of type Task<Option<number[]>, E>
- an async computation that may fail or yields any number of numbers - it gets pretty soon pretty ugly:
// tAp/tMap = Task functor/applicative
// optAp/optMap = Option functor/applicative
// arrAp/arrMap = Array functor/applicative
// tttx = Task(Some([1,2,3]));
// ttty = Task(Some([10,20,30]));
tAp(
tMap(x_ => y_ =>
optAp(
optMap(x => y =>
arrAp(
arrMap(add) (x)) (y)) (x_)) (y_))
(tttx))
(ttty); // Task(Some([11,21,31,12,22,32,13,23,33]))
We can get rid of the anonymous functions by using point-free style, but the computation still remains hideous and confusing:
const comp = f => g => x => f(g(x));
tAp(
tMap(
comp(optAp)
(optMap(
comp(arrAp) (arrMap(add)))))
(tttx))
(ttty); // Task(Some([11,21,31,12,22,32,13,23,33]))
The problem seems to be the common applicative pattern ap(map(f) (x)) (y)
. Let's abstract it:
const liftA2 = ({map, ap}) => f => tx => ty =>
ap(map(f) (tx)) (ty);
const tLiftA2 = liftA2({map: tMap, ap: tAp});
const optLiftA2 = liftA2({map: optMap, ap: optAp});
const arrLiftA2 = liftA2({map: arrMap, ap: arrAp});
comp3(
tLiftA2)
(optLiftA2)
(arrLiftA2)
(add)
(tttx)
(ttty); // Task(Some([11,21,31,12,22,32,13,23,33]))
This is much better. comp3
takes three functions and the resulting composed function takes add
and two composed values tttx
/ttty
and applies add
to the inner values. Since applicative computation of the Array
type means to calculate the cartesian product this is what we get. Nice.
See an running example and how everything falls into place.
If you want to learn more about FP join my course on Github.
Top comments (0)