Functors and Applicatives
in JavaScript???
Let's talk about functions
a → b
Input an a, get out a b
Any input a will map to one output b
Q: When is a function not a function?
A: When it's a method
a, x?, y?, ... → b? c? void?
... something happened inside some object ...
... some side effects happened, maybe affecting state somewhere else?
... and then something returns ... right?
... well, I get the same output for the same input then, right?
Strictly speaking ...
-
-
-
-
Function: Immunize your ???
-
-
A function from Cat → Cat null → Shit
Digression: A few words about function composition
When you build up a functional pipeline, everybody has to play nice.
var capitalize = R.compose(
R.toUpperCase, // String → String
R.prop('textContent'), // Object → String
document.getElementById.bind(document) // String → DOMElement
);
capitalize :: String → String
What if there is no element with the ID you input?
What the Functor
A functor is simply a function that lets you map over the contents of a container.
map :: (a → b) → Functor a → Functor b
So what?
Maybe Functor
function Maybe(x) {
if (!(this instanceof Maybe)) { return new Maybe(x); }
this.value = x;
}
Maybe.prototype.map = function(fn) {
return this.value == null ? this : new Maybe(fn(this.value));
};
Composing with the Maybe Functor
var capitalize = R.compose(
R.map(R.toUpperCase), // Maybe(String) → Maybe(String)
R.map(R.prop('textContent')), // Maybe(Object) → Maybe(String)
Maybe, // Object → Maybe(Object)
document.getElementById.bind(document) // String → DOMElement
);
capitalize :: String → Maybe(String)
- Pro: No need to guard inside the composing functions
- Pro: Output value is now wrapped up, can only be accessed by mapping
- Con: Output value is now wrapped up, can only be accessed by mapping
Some Other Functors
Either
- Has a value (
right
) or a message (left
)
function Either(left, right) {
if (!(this instanceof Either)) { return new Either(left, right); }
this.left = left;
this.right = right;
}
Either.prototype.map = function(f) {
return this.right == null ? this : new Either(this.left, f(this.right));
}
Promise
- The
map
operation is then
map(add1, Promise(2)); //=> Promise(3)
Validation, IO, Reader, Writer, State, Future, ...
Array is a Functor
map :: (a → b) → [a] → [b]
(Arrays are complected with the notion of iteration.)
Apply (Array)
Applies a function (or functions) in a container to a value (or values) in a container.
ap :: [f] → [a] → [f a]
R.ap = curry(function(fns, vs) {
return foldl(function(acc, fn) {
return concat(acc, map(fn, vs));
}, [], fns);
});
Apply (Maybe)
Maybe.prototype.ap = function(vs) {
return (typeof this.value !== 'function') ? new Maybe(null) : vs.map(this.value);
};
Problem: Matching Strings
- We have a list of characters [C]
- We want a function that takes a list of characters [L]
- If the input list [L] contains all of the characters in [C], then it is true, otherwise false
hasAbc :: [String] → Boolean
hasAbc
R.map(R.contains, [C]) //=> [Function: ([String] → Boolean)]
R.ap(R.map(R.contains, [C])) //=> [String] → [Boolean]
R.every(R.I) //=> [Boolean] → Boolean
var hasAbc = R.compose(
R.every(R.I), // [Boolean] → Boolean
R.ap(R.map(R.contains, [C])) // [String] → [Boolean]
);
Not quite right yet.... Is that the right signature for ap
?
Applicative
Wraps an arbitrary object in a container.
of :: x → [x]
-
-
R.of = function(x) {
return [x];
};
hasAbc, take 2
What we want is a function hasAbc :: [String] → Boolean
R.of //=> * → [*]
R.ap(R.map(R.contains, [C])) //=> [[String]] → [Boolean]
R.every(R.I) //=> [Boolean] → Boolean
var hasAbc = R.compose(
R.every(R.I), // [Boolean] → Boolean
R.ap(R.map(R.contains, ['a', 'b', 'c'])), // [[String]] → [Boolean]
R.of // [String] → [[String]]
);
console.log('hasAbc(xyz)',
hasAbc(['x', 'y', 'z'])); // false
console.log('hasAbc(xyzab)',
hasAbc(['x', 'y', 'z', 'a', 'b'])); // false
console.log('hasAbc(cxbyaz)',
hasAbc(['c', 'x', 'b', 'y', 'a', 'z'])); // true
console.log('hasAbc(abcdefg)',
hasAbc(['a', 'b', 'c', 'd', 'e', 'f', 'g'])); // true
Bibliography, Resources, & Further Reading
- Hey Underscore, You're Doing It Wrong, Brian Lonsdorf (@drboolean)
- Hardcore functional programming in Javascript, Leonardo Crespo
- Favoring Curry, Scott Sauyet (@scott_sauyet)
- Why Ramda, Scott Sauyet (@scott_sauyet)
- Introducing Ramda, Michael Hurley (@buzzdecafe)
- The Tao of λ: Applicatives, Ramda style, Michael Hurley (@buzzdecafe)
- Fantasy-Land, Brian McKenna (@puffnfresh) et al.
- Folktale, robotlolita (@robotlolita)
- Ramda.js, Scott Sauyet & Michael Hurley
Illustrations courtesy of J. C. Phillipps (@jcphillipps)
No animals were harmed in the making of this presentation
←
→
/
#