What is applicative functor:
the ability to apply functors to each other.
For example we have tow functors: Container(2), Container(3)
// We can't do this because the numbers are bottled up. add(Container.of(2), Container.of(3)); // NaN
We cannot just add two functors!
Instead we should do:
const map = (fn, m) => m.map(fn);
const containerOfAdd2 = map(add(3), Container.of(2)); // Container(5)
or
Container.of(2).chain(two => Container.of(3).map(add(two)));
Previous solution should work. but there are better way to do it:
1. ap
Container.prototype.ap = function (otherContainer) { return otherContainer.map(this.$value); };
As you can see, 'ap' takes a fuctor then applya map to it.
We can see ap:
Container.of(2).map(add).ap(Container.of(3)); // Container(5)
Or, we add lift 'add(2)' into Container, then apply Container(3):
Container.of(add(2)).ap(Container.of(3)); // Container(5)
Because 'add' is partially applied in add(2), when doing '.ap(Container.of(3))', we give the rest input '3' to it.
Now, we can define applicative functor in programming language:
An applicative functor is a pointed functor with an
apmethod
Note the dependence on pointed.
Laws behind:
F.of(x).map(f) === F.of(f).ap(F.of(x))
Main idea is: lift 'f' (function) into Functor, then 'ap' (apply) another Functor with the value (x).
Some example:
Maybe.of(add).ap(Maybe.of(2)).ap(Maybe.of(3)) // Just(5) Task.of(add).ap(Task.of(2)).ap(Task.of(3)) // Task(5)
Equals:
Maybe.of(add(2)).ap(Maybe.of(3)) // Just(5) Task.of(add(2)).ap(Task.of(3)) // Task(5)
More examples:
// Http.get :: String -> Task Error HTML const renderPage = curry((destinations, events) => { /* render page */ }); Task.of(renderPage).ap(Http.get('/destinations')).ap(Http.get('/events')); // Task("<div>some page with dest and events</div>")
// $ :: String -> IO DOM const $ = selector => new IO(() => document.querySelector(selector)); // getVal :: String -> IO String const getVal = compose(map(prop('value')), $); // signIn :: String -> String -> Bool -> User const signIn = curry((username, password, rememberMe) => { /* signing in */ }); IO.of(signIn).ap(getVal('#email')).ap(getVal('#password')).ap(IO.of(false)); // IO({ id: 3, email: 'gg@allin.com' })
----
const R = require('ramda');
class Container {
static of(x) {
return new Container(x);
}
constructor(x) {
this.$value = x;
}
map (fn) {
return Container.of(fn(this.$value));
}
ap (functor) {
return functor.map(this.$value);
}
join() {
return this.$value;
}
chain(fn) {
return this.map(fn).join();
}
inspect() {
return `Container(${this.$value})`;
}
}
class Maybe {
get isNothing() {
return this.$value === null || this.$value === undefined;
}
get isJust() {
return !this.isNothing;
}
constructor(x) {
this.$value = x;
}
inspect() {
return this.isNothing ? 'Nothing' : `Just(${this.$value})`;
}
// ----- Pointed Maybe
static of(x) {
return new Maybe(x);
}
// ----- Functor Maybe
map(fn) {
return this.isNothing ? this : Maybe.of(fn(this.$value));
}
// ----- Applicative Maybe
ap(f) {
return this.isNothing ? this : f.map(this.$value);
}
// ----- Monad Maybe
chain(fn) {
return this.map(fn).join();
}
join() {
return this.isNothing ? this : this.$value;
}
// ----- Traversable Maybe
sequence(of) {
this.traverse(of, identity);
}
traverse(of, fn) {
return this.isNothing ? of(this) : fn(this.$value).map(Maybe.of);
}
}
const add = a => b => a + b;
const map = (fn, m) => m.map(fn);
const notWorking = add(Container.of(2), Container.of(3));
const containerOfAdd2 = map(add(3), Container.of(2));
console.log(containerOfAdd2); // Contianer(5)
const works = Container.of(2).chain(v => Container.of(3).map(add(v)));
console.log(works); // Contianer(5)
const ap = Container.of(2).map(add).ap(Container.of(3));
console.log(ap)
const ap2 = Container.of(add(2)).ap(Container.of(3));
console.log(Maybe.of(add).ap(Maybe.of(2)).ap(Maybe.of(3)))
console.log(Maybe.of(add(2)).ap(Maybe.of(3)))