Let's examine a pointfree way to write these applicative calls. Since we know map
is equal to of/ap
, we can write generic functions that will ap
as many times as we specify:
const liftA2 = curry((g, f1, f2) => f1.map(g).ap(f2)); const liftA3 = curry((g, f1, f2, f3) => f1.map(g).ap(f2).ap(f3)); // liftA4, etc
Let's see the previous examples written this way:
const profile = name => email => `${name}__${email}`; const safeProfile = liftA2(profile); const res1 = safeProfile(prop('name', user), prop('email', user)); // John Doe__blurp_blurp
liftA2(add, Maybe.of(2), Maybe.of(3)); // Maybe(5) liftA2(renderPage, Http.get('/destinations'), Http.get('/events')); // Task('<div>some page with dest and events</div>') liftA3(signIn, getVal('#email'), getVal('#password'), IO.of(false)); // IO({ id: 3, email: 'gg@allin.com' })
liftAN: Lift a curry function into a Functor context, which will be define later;
liftA2(add, Maybe.of(2), Maybe.of(3)); Maybe will be the Functor context for 'add' function which has been lifted
Laws:
Identity
// identity A.of(id).ap(v) === v;
For example:
const v = Identity.of('Pillow Pets'); Identity.of(id).ap(v) === v;
Homomorphism
// homomorphism A.of(f).ap(A.of(x)) === A.of(f(x));
A homomorphism is just a structure preserving map. In fact, a functor is just a homomorphism between categories as it preserves the original category's structure under the mapping.
A quick example:
Either.of(toUpperCase).ap(Either.of('oreos')) === Either.of(toUpperCase('oreos'));
Interchange
The interchange law states that it doesn't matter if we choose to lift our function into the left or right side of ap
.
// interchange v.ap(A.of(x)) === A.of(f => f(x)).ap(v);
Here is an example:
const v = Task.of(reverse); const x = 'Sparklehorse'; v.ap(Task.of(x)) === Task.of(f => f(x)).ap(v);
Composition
// composition A.of(compose).ap(u).ap(v).ap(w) === u.ap(v.ap(w));
const u = IO.of(toUpperCase); const v = IO.of(concat('& beyond')); const w = IO.of('blood bath '); IO.of(compose).ap(u).ap(v).ap(w) === u.ap(v.ap(w));
Examples:
const safeAdd = curry((a, b) => Maybe.of(add).ap(a).ap(b)); const safeAdd = liftA2(add); const localStorage = { player1: { id:1, name: 'Albert' }, player2: { id:2, name: 'Theresa' }, }; // getFromCache :: String -> IO User const getFromCache = x => new IO(() => localStorage[x]); // game :: User -> User -> String const game = curry((p1, p2) => `${p1.name} vs ${p2.name}`); // startGame :: IO String const startGame = liftA2(game, getFromCache('player1'), getFromCache('player2'));