Functor
An object that implements a map
function which, while running over each value in the object to produce a new object, adheres to two rules:
Preserves identity
object.map(x => x) ≍ object
Composable
object.map(compose(f, g)) ≍ object.map(g).map(f)
(f
, g
are arbitrary functions)
A common functor in JavaScript is Array
since it abides to the two functor rules:
;[1, 2, 3].map(x => x) // = [1, 2, 3]
and
const f = x => x + 1
const g = x => x * 2
;[1, 2, 3].map(x => f(g(x))) // = [3, 5, 7]
;[1, 2, 3].map(g).map(f) // = [3, 5, 7]
Pointed Functor
An object with an of
function that puts any single value into it.
ES2015 adds Array.of
making arrays a pointed functor.
Lifting
Lifting is when you take a value and put it into an object like a functor. If you lift a function into an Applicative Functor then you can make it work on values that are also in that functor.
Some implementations have a function called lift
, or liftA2
to make it easier to run functions on functors.
const liftA2 = (f) => (a, b) => a.map(f).ap(b) // note it's `ap` and not `map`.
const mult = a => b => a * b
const liftedMult = liftA2(mult) // this function now works on functors like array
liftedMult([1, 2], [3]) // [3, 6]
liftA2(a => b => a + b)([1, 2], [3, 4]) // [4, 5, 5, 6]
Lifting a one-argument function and applying it does the same thing as map
.
const increment = (x) => x + 1
lift(increment)([2]) // [3]
;[2].map(increment) // [3]
Monoid
An object with a function that "combines" that object with another of the same type.
One simple monoid is the addition of numbers:
In this case number is the object and +
is the function.
An "identity" value must also exist that when combined with a value doesn't change it.
The identity value for addition is 0
.
It's also required that the grouping of operations will not affect the result (associativity):
1 + (2 + 3) === (1 + 2) + 3 // true
Array concatenation also forms a monoid:
;[1, 2].concat([3, 4]) // [1, 2, 3, 4]
The identity value is empty array []
;[1, 2].concat([]) // [1, 2]
If identity and compose functions are provided, functions themselves form a monoid:
const identity = (a) => a
const compose = (f, g) => (x) => f(g(x))
foo
is any function that takes one argument.
compose(foo, identity) ≍ compose(identity, foo) ≍ foo
Monad
A monad is an object with of
and chain
functions. chain
is like map
except it un-nests the resulting nested object.
// Implementation
Array.prototype.chain = function (f) {
return this.reduce((acc, it) => acc.concat(f(it)), [])
}
// Usage
Array.of('cat,dog', 'fish,bird').chain((a) => a.split(',')) // ['cat', 'dog', 'fish', 'bird']
// Contrast to map
Array.of('cat,dog', 'fish,bird').map((a) => a.split(',')) // [['cat', 'dog'], ['fish', 'bird']]
of
is also known as return
in other functional languages.
chain
is also known as flatmap
and bind
in other languages.
Comonad
An object that has extract
and extend
functions.
const CoIdentity = (v) => ({
val: v,
extract () {
return this.val
},
extend (f) {
return CoIdentity(f(this))
}
})
Extract takes a value out of a functor.
CoIdentity(1).extract() // 1
Extend runs a function on the comonad. The function should return the same type as the comonad.
CoIdentity(1).extend((co) => co.extract() + 1) // CoIdentity(2)
Applicative Functor
An applicative functor is an object with an ap
function. ap
applies a function in the object to a value in another object of the same type.
// Implementation
Array.prototype.ap = function (xs) {
return this.reduce((acc, f) => acc.concat(xs.map(f)), [])
}
// Example usage
;[(a) => a + 1].ap([1]) // [2]
This is useful if you have two objects and you want to apply a binary function to their contents.
// Arrays that you want to combine
const arg1 = [1, 3]
const arg2 = [4, 5]
// combining function - must be curried for this to work
const add = (x) => (y) => x + y
const partiallyAppliedAdds = [add].ap(arg1) // [(y) => 1 + y, (y) => 3 + y]
This gives you an array of functions that you can call ap
on to get the result:
partiallyAppliedAdds.ap(arg2) // [5, 6, 7, 8]
Setoid
An object that has an equals
function which can be used to compare other objects of the same type.
Make array a setoid:
Array.prototype.equals = function (arr) {
const len = this.length
if (len !== arr.length) {
return false
}
for (let i = 0; i < len; i++) {
if (this[i] !== arr[i]) {
return false
}
}
return true
}
;[1, 2].equals([1, 2]) // true
;[1, 2].equals([0]) // false
Semigroup
An object that has a concat
function that combines it with another object of the same type.
;[1].concat([2]) // [1, 2]
Foldable
An object that has a reduce
function that applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value.
const sum = (list) => list.reduce((acc, val) => acc + val, 0)
sum([1, 2, 3]) // 6
Lens
A lens is a structure (often an object or function) that pairs a getter and a non-mutating setter for some other data
structure.
// Using [Ramda's lens](http://ramdajs.com/docs/#lens)
const nameLens = R.lens(
// getter for name property on an object
(obj) => obj.name,
// setter for name property
(val, obj) => Object.assign({}, obj, {name: val})
)
Having the pair of get and set for a given data structure enables a few key features.
const person = {name: 'Gertrude Blanch'}
// invoke the getter
R.view(nameLens, person) // 'Gertrude Blanch'
// invoke the setter
R.set(nameLens, 'Shafi Goldwasser', person) // {name: 'Shafi Goldwasser'}
// run a function on the value in the structure
R.over(nameLens, uppercase, person) // {name: 'GERTRUDE BLANCH'}
Lenses are also composable. This allows easy immutable updates to deeply nested data.
// This lens focuses on the first item in a non-empty array
const firstLens = R.lens(
// get first item in array
xs => xs[0],
// non-mutating setter for first item in array
(val, [__, ...xs]) => [val, ...xs]
)
const people = [{name: 'Gertrude Blanch'}, {name: 'Shafi Goldwasser'}]
// Despite what you may assume, lenses compose left-to-right.
R.over(compose(firstLens, nameLens), uppercase, people) // [{'name': 'GERTRUDE BLANCH'}, {'name': 'Shafi Goldwasser'}]
Other implementations: