(aka "Algebraic JavaScript Specification")
This project specifies the behavior of a number of methods that may optionally be added to any object. The motivation behind this is to encourage greater code reuse. You can create functions that just rely on objects having implementations of the methods below, and in doing so you can make them work with a wide variety of different, but related data structures.
For the purposes of this, spec, an "entity" is an object that has an [[equivalent]]
operation (see bellow) and may implement some or all of the other methods.
A "value" is any JavaScript value, this includes entities.
[[equivalent]] is an imaginary function that takes two arguments and returns true
or false
. It should return true
iff the two objects could be swapped and no program would be able to tell the difference other than by checking the exact object references.
Examples:
- Two numbers are equivalent if they are equal
- Two strings are equivalent if they are equal
- Two arrays are equivalent if they contain equivalent items in the same order
- Two functions (that have no side effects) are equivalent if they return equivalent outputs for equivalent inputs.
If an entity (call it A
) implements a method concat
then when it is passed one argument that is of the same class as A
it must:
- return a value with the same type as
A
(the object on which the method was called). - It must be associative. That is to say
a.concat(b).concat(c)
must be equivalent toa.concat(b.concat(c))
( associativity )
( an entity that implements concat
is also known as a 'semigroup' in functional programming )
Any entity that implements empty
must also implement concat
. A.constructor.empty()
must return an entity of the same class as A
which obeys the following two rules.
A
is equivalent toA.concat(A.constructor.empty())
( right identity )A
is equivalent toA.constructor.empty().concat(A)
( left identity )
( an entity that implements empty
is also known as a 'monoid' in functional programming )
If an entity (call it A
) implements a method map
then when it is passed one argument that is a function, map must return an entity of the same class as A
.
map
must also obey the following two rules:
A.map(function (a) { return a; })
is equivalent toA
( identity )A.map(function (x) { return f(g(x)); })
is equivalent toA.map(g).map(f)
( composition )
A practical example of how map
might be implemented is the array, which calls map for each element in the array and returns a new array that is an array of the results of the function, e.g.:
var arr = [1, 2, 3, 4];
function square(a) {
return a * a;
}
var res = arr.map(square);
res
would then be [1, 4, 9, 16]
( an entity that implements map
is also known as a 'functor' in functional programming )
If an entity (call it A
) implements a method chain
then when it is passed one argument that is a function and that function returns an entity of the same class as A
, chain must return an entity of the same class as A
.
It must also obey the rule:
A.chain(f).chain(g)
is equivalent toA.chain(function (x) { return f(x).chain(g); })
( associativity )
Any entity that implements of
must also implement chain
. A.constructor.of(B)
must return an entity of the same class as A
which obeys the following three rules.
- If
f
is a function that returns an entity of classA
, thenA.of(b).chain(f)
is equivalent tof(b)
( left identity ) A.chain(A.constructor.of)
is equivalent toA
( right identity )- Nothing about the contents of
A
may be checked, it is to be treated as an opaque value
( an entity that implements of
is also known as a 'monad' in functional programming )
At least as far as
of
goes,of
is definitely useful in the absence ofchain
(after all, Applicative is defined in terms ofpure
andap
, wherepure
is ourof
andap
is not the same thing aschain
). Whether or notof
makes sense withoutap
is another issue.The point about
zero
makes sense. Thanks for correcting me!