(aka "Algebraic JavaScript Specification")
This project specifies interoperability of common algebraic structures:
An algebra is a set of values, a set of operators that it is closed under and some laws it must obey.
Each Fantasy Land algebra is a separate specification. An algebra may have dependencies on other algebras which must be implemented. An algebra may also state other algebra methods which do not need to be implemented and how they can be derived from new methods.
- "value" is any JavaScript value, including any which have the structures defined below.
- "equivalent" is an appropriate definition of equivalence for the given value.
The definition should ensure that the two values can be safely swapped out in a program that respects abstractions. For example:
- Two lists are equivalent if they are equivalent at all indices.
- Two plain old JavaScript objects, interpreted as dictionaries, are equivalent when they are equivalent for all keys.
- Two promises are equivalent when they yield equivalent values.
- Two functions are equivalent if they yield equivalent outputs for equivalent inputs.
- Add
fantasy-landpackage as a peer dependency. Add in yourpackage.json:
{
...
"peerDependencies": {
"fantasy-land": "*"
},
...
}- The
fantasy-landpackage exposes method names, you should use them for you Fantasy Land methods:
var fl = require('fantasy-land')
// ...
MyType.prototype[fl.map] = function(fn) {
// Here goes implementation of map for your type...
}- Add library npm package, and
fantasy-landas your normal dependecies:
{
...
"dependencies": {
"some-fl-compatible-lib": "1.0.0",
"fantasy-land": "1.0.0"
},
...
}- If you don't want to access Fantasy Land methods directly
(for example if you use two libraries that talk to each other using Fantasy Land),
then that's it — simply install them and use as you normally would,
only install
fantasy-landpackage as well.
If you do want to access Fantasy Land methods, do it like this:
var fl = require('fantasy-land')
var Something = require('some-fl-compatible-lib')
var foo = new Something(1)
var bar = foo[fl.map](x => x + 1)a.equals(a) === true(reflexivity)a.equals(b) === b.equals(a)(symmetry)- If
a.equals(b)andb.equals(c), thena.equals(c)(transitivity)
A value which has a Setoid must provide an equals method. The
equals method takes one argument:
a.equals(b)
-
bmust be a value of the same Setoid- If
bis not the same Setoid, behaviour ofequalsis unspecified (returningfalseis recommended).
- If
-
equalsmust return a boolean (trueorfalse).
a.concat(b).concat(c)is equivalent toa.concat(b.concat(c))(associativity)
A value which has a Semigroup must provide a concat method. The
concat method takes one argument:
s.concat(b)
-
bmust be a value of the same Semigroup- If
bis not the same semigroup, behaviour ofconcatis unspecified.
- If
-
concatmust return a value of the same Semigroup.
A value that implements the Monoid specification must also implement the Semigroup specification.
m.concat(m.empty())is equivalent tom(right identity)m.empty().concat(m)is equivalent tom(left identity)
A value which has a Monoid must provide an empty method on itself or
its constructor object. The empty method takes no arguments:
m.empty()
m.constructor.empty()
emptymust return a value of the same Monoid
u.map(a => a)is equivalent tou(identity)u.map(x => f(g(x)))is equivalent tou.map(g).map(f)(composition)
A value which has a Functor must provide a map method. The map
method takes one argument:
u.map(f)
-
fmust be a function,- If
fis not a function, the behaviour ofmapis unspecified. fcan return any value.
- If
-
mapmust return a value of the same Functor
A value that implements the Apply specification must also implement the Functor specification.
a.map(f => g => x => f(g(x))).ap(u).ap(v)is equivalent toa.ap(u.ap(v))(composition)
A value which has an Apply must provide an ap method. The ap
method takes one argument:
a.ap(b)
-
amust be an Apply of a function,- If
adoes not represent a function, the behaviour ofapis unspecified.
- If
-
bmust be an Apply of any value -
apmust apply the function in Applyato the value in Applyb
A value that implements the Applicative specification must also implement the Apply specification.
A value which satisfies the specification of an Applicative does not need to implement:
- Functor's
map; derivable asfunction(f) { return this.of(f).ap(this); }
a.of(x => x).ap(v)is equivalent tov(identity)a.of(f).ap(a.of(x))is equivalent toa.of(f(x))(homomorphism)u.ap(a.of(y))is equivalent toa.of(f => f(y)).ap(u)(interchange)
A value which has an Applicative must provide an of method on itself
or its constructor object. The of method takes one argument:
a.of(b)
a.constructor.of(b)
-
ofmust provide a value of the same Applicative- No parts of
bshould be checked
- No parts of
u.reduceis equivalent tou.toArray().reduce
toArray; derivable asfunction() { return this.reduce((acc, x) => acc.concat([x]), []); }
A value which has a Foldable must provide a reduce method. The reduce
method takes two arguments:
u.reduce(f, x)
-
fmust be a binary function- if
fis not a function, the behaviour ofreduceis unspecified. - The first argument to
fmust be the same type asx. fmust return a value of the same type asx
- if
-
xis the initial accumulator value for the reduction
A value that implements the Traversable specification must also implement the Functor specification.
-
t(u.sequence(f.of))is equivalent tou.map(t).sequence(g.of)wheretis a natural transformation fromftog(naturality) -
u.map(x => Id(x)).sequence(Id.of)is equivalent toId.of(u)(identity) -
u.map(Compose).sequence(Compose.of)is equivalent toCompose(u.sequence(f.of).map(x => x.sequence(g.of)))(composition)
traverse; derivable asfunction(f, of) { return this.map(f).sequence(of); }
A value which has a Traversable must provide a sequence method. The sequence
method takes one argument:
u.sequence(of)
ofmust return the Applicative thatucontains.
A value that implements the Chain specification must also implement the Apply specification.
A value which satisfies the specification of a Chain does not need to implement:
- Apply's
ap; derivable asfunction ap(m) { return this.chain(f => m.map(f)); }
m.chain(f).chain(g)is equivalent tom.chain(x => f(x).chain(g))(associativity)
A value which has a Chain must provide a chain method. The chain
method takes one argument:
m.chain(f)
-
fmust be a function which returns a value- If
fis not a function, the behaviour ofchainis unspecified. fmust return a value of the same Chain
- If
-
chainmust return a value of the same Chain
A value that implements the Monad specification must also implement the Applicative and Chain specifications.
A value which satisfies the specification of a Monad does not need to implement:
- Apply's
ap; derivable asfunction(m) { return this.chain(f => m.map(f)); } - Functor's
map; derivable asfunction(f) { var m = this; return m.chain(a => m.of(f(a)))}
m.of(a).chain(f)is equivalent tof(a)(left identity)m.chain(m.of)is equivalent tom(right identity)
w.extend(g).extend(f)is equivalent tow.extend(_w => f(_w.extend(g)))
An Extend must provide an extend method. The extend
method takes one argument:
w.extend(f)
-
fmust be a function which returns a value- If
fis not a function, the behaviour ofextendis unspecified. fmust return a value of typev, for some variablevcontained inw.
- If
-
extendmust return a value of the same Extend.
A value that implements the Comonad specification must also implement the Functor and Extend specifications.
w.extend(_w => _w.extract())is equivalent toww.extend(f).extract()is equivalent tof(w)w.extend(f)is equivalent tow.extend(x => x).map(f)
A value which has a Comonad must provide an extract method on itself.
The extract method takes no arguments:
c.extract()
extractmust return a value of typev, for some variablevcontained inw.vmust have the same type thatfreturns inextend.
- If there's more than a single way to implement the methods and laws, the implementation should choose one and provide wrappers for other uses.
- It's discouraged to overload the specified methods. It can easily result in broken and buggy behaviour.
- It is recommended to throw an exception on unspecified behaviour.
- An
Idcontainer which implements all methods is provided inid.js.