Fantasy Land Specification
(aka "Algebraic JavaScript Specification")
This project specifies interoperability of common algebraic structures:
- Setoid
- Ord
- Semigroupoid
- Category
- Semigroup
- Monoid
- Group
- Filterable
- Functor
- Contravariant
- Apply
- Applicative
- Alt
- Plus
- Alternative
- Foldable
- Traversable
- Chain
- ChainRec
- Monad
- Extend
- Comonad
- Bifunctor
- Profunctor
General
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.
Terminology
- "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.
Type signature notation
The type signature notation used in this document is described below:1
::"is a member of".e :: tcan be read as: "the expressioneis a member of typet".true :: Boolean- "trueis a member of typeBoolean".42 :: Integer, Number- "42is a member of typeInteger and Number".
- New types can be created via type constructors.
- Type constructors can take zero or more type arguments.
Arrayis a type constructor which takes one type argument.Array Stringis the type of all arrays of strings. Each of the following has typeArray String:[],['foo', 'bar', 'baz'].Array (Array String)is the type of all arrays of arrays of strings. Each of the following has typeArray (Array String):[],[ [], [] ],[ [], ['foo'], ['bar', 'baz'] ].
- Lowercase letters stand for type variables.
- Type variables can take any type unless they have been restricted by means of type constraints (see fat arrow below).
->(arrow) Function type constructor.->is an infix type constructor that takes two type arguments where left argument is the input type and the right argument is the output type.->'s input type can be a grouping of types to create the type of a function which accepts zero or more arguments. The syntax is:(<input-types>) -> <output-type>, where<input-types>comprises zero or more comma–space (,)-separated type representations and parens may be omitted for unary functions.String -> Array Stringis a type satisfied by functions which take aStringand return anArray String.String -> Array String -> Array Stringis a type satisfied by functions which take aStringand return a function which takes anArray Stringand returns anArray String.(String, Array String) -> Array Stringis a type satisfied by functions which take aStringand anArray Stringas arguments and return anArray String.() -> Numberis a type satisfied by functions which do not take arguments and return aNumber.
~>(squiggly arrow) Method type constructor.- When a function is a property of an Object, it is called a method. All methods have an implicit parameter type - the type of which they are a property.
a ~> a -> ais a type satisfied by methods on Objects of typeawhich take a typeaas an argument and return a value of typea.
=>(fat arrow) Expresses constraints on type variables.- In
a ~> a -> a(see squiggly arrow above),acan be of any type.Semigroup a => a ~> a -> aadds a constraint such that the typeamust now satisfy theSemigrouptypeclass. To satisfy a typeclass means to lawfully implement all functions/methods specified by that typeclass.
- In
For example:
traverse :: Applicative f, Traversable t => t a ~> (TypeRep f, a -> f b) -> f (t b)
'------' '--------------------------' '-' '-------------------' '-----'
' ' ' ' '
' ' - type constraints ' ' - argument types ' - return type
' '
'- method name ' - method target type
Prefixed method names
In order for a data type to be compatible with Fantasy Land, its values must
have certain properties. These properties are all prefixed by fantasy-land/.
For example:
// MyType#fantasy-land/map :: MyType a ~> (a -> b) -> MyType b
MyType.prototype['fantasy-land/map'] = ...Further in this document unprefixed names are used just to reduce noise.
For convenience you can use fantasy-land package:
var fl = require('fantasy-land')
// ...
MyType.prototype[fl.map] = ...
// ...
var foo = bar[fl.map](x => x + 1)Type representatives
Certain behaviours are defined from the perspective of a member of a type.
Other behaviours do not require a member. Thus certain algebras require a
type to provide a value-level representative (with certain properties). The
Identity type, for example, could provide Id as its type representative:
Id :: TypeRep Identity.
If a type provides a type representative, each member of the type must have
a constructor property which is a reference to the type representative.
Algebras
Setoid
a.equals(a) === true(reflexivity)a.equals(b) === b.equals(a)(symmetry)- If
a.equals(b)andb.equals(c), thena.equals(c)(transitivity)
equals method
equals :: Setoid a => a ~> a -> BooleanA 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).
Ord
A value that implements the Ord specification must also implement the Setoid specification.
a.lte(b)orb.lte(a)(totality)- If
a.lte(b)andb.lte(a), thena.equals(b)(antisymmetry) - If
a.lte(b)andb.lte(c), thena.lte(c)(transitivity)
lte method
lte :: Ord a => a ~> a -> BooleanA value which has an Ord must provide a lte method. The
lte method takes one argument:
a.lte(b)
-
bmust be a value of the same Ord- If
bis not the same Ord, behaviour oflteis unspecified (returningfalseis recommended).
- If
-
ltemust return a boolean (trueorfalse).
Semigroupoid
a.compose(b).compose(c) === a.compose(b.compose(c))(associativity)
compose method
compose :: Semigroupoid c => c i j ~> c j k -> c i kA value which has a Semigroupoid must provide a compose method. The
compose method takes one argument:
a.compose(b)
-
bmust be a value of the same Semigroupoid- If
bis not the same semigroupoid, behaviour ofcomposeis unspecified.
- If
-
composemust return a value of the same Semigroupoid.
Category
A value that implements the Category specification must also implement the Semigroupoid specification.
a.compose(C.id())is equivalent toa(right identity)C.id().compose(a)is equivalent toa(left identity)
id method
id :: Category c => () -> c a aA value which has a Category must provide an id function on its
type representative:
C.id()
Given a value c, one can access its type representative via the
constructor property:
c.constructor.id()
idmust return a value of the same Category
Semigroup
a.concat(b).concat(c)is equivalent toa.concat(b.concat(c))(associativity)
concat method
concat :: Semigroup a => a ~> a -> aA 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.
Monoid
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)
empty method
empty :: Monoid m => () -> mA value which has a Monoid must provide an empty function on its
type representative:
M.empty()
Given a value m, one can access its type representative via the
constructor property:
m.constructor.empty()
emptymust return a value of the same Monoid
Group
A value that implements the Group specification must also implement the Monoid specification.
g.concat(g.invert())is equivalent tog.constructor.empty()(right inverse)g.invert().concat(g)is equivalent tog.constructor.empty()(left inverse)
invert method
invert :: Group g => g ~> () -> gA value which has a Group must provide an invert method. The
invert method takes no arguments:
g.invert()
invertmust return a value of the same Group.
Filterable
v.filter(x => p(x) && q(x))is equivalent tov.filter(p).filter(q)(distributivity)v.filter(x => true)is equivalent tov(identity)v.filter(x => false)is equivalent tow.filter(x => false)ifvandware values of the same Filterable (annihilation)
filter method
filter :: Filterable f => f a ~> (a -> Boolean) -> f aA value which has a Filterable must provide a filter method. The filter
method takes one argument:
v.filter(p)
-
pmust be a function.- If
pis not a function, the behaviour offilteris unspecified. pmust return eithertrueorfalse. If it returns any other value, the behaviour offilteris unspecified.
- If
-
filtermust return a value of the same Filterable.
Functor
u.map(a => a)is equivalent tou(identity)u.map(x => f(g(x)))is equivalent tou.map(g).map(f)(composition)
map method
map :: Functor f => f a ~> (a -> b) -> f bA 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.- No parts of
f's return value should be checked.
- If
-
mapmust return a value of the same Functor
Contravariant
u.contramap(a => a)is equivalent tou(identity)u.contramap(x => f(g(x)))is equivalent tou.contramap(f).contramap(g)(composition)
contramap method
contramap :: Contravariant f => f a ~> (b -> a) -> f bA value which has a Contravariant must provide a contramap method. The
contramap method takes one argument:
u.contramap(f)
-
fmust be a function,- If
fis not a function, the behaviour ofcontramapis unspecified. fcan return any value.- No parts of
f's return value should be checked.
- If
-
contramapmust return a value of the same Contravariant
Apply
A value that implements the Apply specification must also implement the Functor specification.
v.ap(u.ap(a.map(f => g => x => f(g(x)))))is equivalent tov.ap(u).ap(a)(composition)
ap method
ap :: Apply f => f a ~> f (a -> b) -> f bA value which has an Apply must provide an ap method. The ap
method takes one argument:
a.ap(b)
-
bmust be an Apply of a function- If
bdoes not represent a function, the behaviour ofapis unspecified. bmust be same Apply asa.
- If
-
amust be an Apply of any value -
apmust apply the function in Applybto the value in Applya- No parts of return value of that function should be checked.
-
The
Applyreturned byapmust be the same asaandb
Applicative
A value that implements the Applicative specification must also implement the Apply specification.
v.ap(A.of(x => x))is equivalent tov(identity)A.of(x).ap(A.of(f))is equivalent toA.of(f(x))(homomorphism)A.of(y).ap(u)is equivalent tou.ap(A.of(f => f(y)))(interchange)
of method
of :: Applicative f => a -> f aA value which has an Applicative must provide an of function on its
type representative. The of function takes
one argument:
F.of(a)
Given a value f, one can access its type representative via the
constructor property:
f.constructor.of(a)
-
ofmust provide a value of the same Applicative- No parts of
ashould be checked
- No parts of
Alt
A value that implements the Alt specification must also implement the Functor specification.
a.alt(b).alt(c)is equivalent toa.alt(b.alt(c))(associativity)a.alt(b).map(f)is equivalent toa.map(f).alt(b.map(f))(distributivity)
alt method
alt :: Alt f => f a ~> f a -> f aA value which has a Alt must provide a alt method. The
alt method takes one argument:
a.alt(b)
-
bmust be a value of the same Alt- If
bis not the same Alt, behaviour ofaltis unspecified. aandbcan contain any value of same type.- No parts of
a's andb's containing value should be checked.
- If
-
altmust return a value of the same Alt.
Plus
A value that implements the Plus specification must also implement the Alt specification.
x.alt(A.zero())is equivalent tox(right identity)A.zero().alt(x)is equivalent tox(left identity)A.zero().map(f)is equivalent toA.zero()(annihilation)
zero method
zero :: Plus f => () -> f aA value which has a Plus must provide an zero function on its
type representative:
A.zero()
Given a value x, one can access its type representative via the
constructor property:
x.constructor.zero()
zeromust return a value of the same Plus
Alternative
A value that implements the Alternative specification must also implement the Applicative and Plus specifications.
x.ap(f.alt(g))is equivalent tox.ap(f).alt(x.ap(g))(distributivity)x.ap(A.zero())is equivalent toA.zero()(annihilation)
Foldable
u.reduceis equivalent tou.reduce((acc, x) => acc.concat([x]), []).reduce
reduce method
reduce :: Foldable f => f a ~> ((b, a) -> b, b) -> bA 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.- No parts of
f's return value should be checked.
- if
-
xis the initial accumulator value for the reduction- No parts of
xshould be checked.
- No parts of
Traversable
A value that implements the Traversable specification must also implement the Functor and Foldable specifications.
-
t(u.traverse(F, x => x))is equivalent tou.traverse(G, t)for anytsuch thatt(a).map(f)is equivalent tot(a.map(f))(naturality) -
u.traverse(F, F.of)is equivalent toF.of(u)for any ApplicativeF(identity) -
u.traverse(Compose, x => new Compose(x))is equivalent tonew Compose(u.traverse(F, x => x).map(x => x.traverse(G, x => x)))forComposedefined below and any ApplicativesFandG(composition)
var Compose = function(c) {
this.c = c;
};
Compose.of = function(x) {
return new Compose(F.of(G.of(x)));
};
Compose.prototype.ap = function(f) {
return new Compose(this.c.ap(f.c.map(u => y => y.ap(u))));
};
Compose.prototype.map = function(f) {
return new Compose(this.c.map(y => y.map(f)));
};traverse method
traverse :: Applicative f, Traversable t => t a ~> (TypeRep f, a -> f b) -> f (t b)A value which has a Traversable must provide a traverse method. The traverse
method takes two arguments:
u.traverse(A, f)
-
Amust be the type representative of an Applicative. -
fmust be a function which returns a value- If
fis not a function, the behaviour oftraverseis unspecified. fmust return a value of the type represented byA.
- If
-
traversemust return a value of the type represented byA.
Chain
A value that implements the Chain specification must also implement the Apply specification.
m.chain(f).chain(g)is equivalent tom.chain(x => f(x).chain(g))(associativity)
chain method
chain :: Chain m => m a ~> (a -> m b) -> m bA 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
ChainRec
A value that implements the ChainRec specification must also implement the Chain specification.
M.chainRec((next, done, v) => p(v) ? d(v).map(done) : n(v).map(next), i)is equivalent to(function step(v) { return p(v) ? d(v) : n(v).chain(step); }(i))(equivalence)- Stack usage of
M.chainRec(f, i)must be at most a constant multiple of the stack usage offitself.
chainRec method
chainRec :: ChainRec m => ((a -> c, b -> c, a) -> m c, a) -> m bA Type which has a ChainRec must provide a chainRec function on its
type representative. The chainRec function
takes two arguments:
M.chainRec(f, i)
Given a value m, one can access its type representative via the
constructor property:
m.constructor.chainRec(f, i)
fmust be a function which returns a value- If
fis not a function, the behaviour ofchainRecis unspecified. ftakes three argumentsnext,done,valuenextis a function which takes one argument of same type asiand can return any valuedoneis a function which takes one argument and returns the same type as the return value ofnextvalueis some value of the same type asi
fmust return a value of the same ChainRec which contains a value returned from eitherdoneornext
- If
chainRecmust return a value of the same ChainRec which contains a value of same type as argument ofdone
Monad
A value that implements the Monad specification must also implement the Applicative and Chain specifications.
M.of(a).chain(f)is equivalent tof(a)(left identity)m.chain(M.of)is equivalent tom(right identity)
Extend
A value that implements the Extend specification must also implement the Functor specification.
w.extend(g).extend(f)is equivalent tow.extend(_w => f(_w.extend(g)))
extend method
extend :: Extend w => w a ~> (w a -> b) -> w bAn 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.- No parts of
f's return value should be checked.
- If
-
extendmust return a value of the same Extend.
Comonad
A value that implements the Comonad specification must also implement the Extend specification.
w.extend(_w => _w.extract())is equivalent tow(left identity)w.extend(f).extract()is equivalent tof(w)(right identity)
extract method
extract :: Comonad w => w a ~> () -> aA value which has a Comonad must provide an extract method on itself.
The extract method takes no arguments:
w.extract()
extractmust return a value of typev, for some variablevcontained inw.vmust have the same type thatfreturns inextend.
Bifunctor
A value that implements the Bifunctor specification must also implement the Functor specification.
p.bimap(a => a, b => b)is equivalent top(identity)p.bimap(a => f(g(a)), b => h(i(b))is equivalent top.bimap(g, i).bimap(f, h)(composition)
bimap method
bimap :: Bifunctor f => f a c ~> (a -> b, c -> d) -> f b dA value which has a Bifunctor must provide a bimap method. The bimap
method takes two arguments:
c.bimap(f, g)
-
fmust be a function which returns a value- If
fis not a function, the behaviour ofbimapis unspecified. fcan return any value.- No parts of
f's return value should be checked.
- If
-
gmust be a function which returns a value- If
gis not a function, the behaviour ofbimapis unspecified. gcan return any value.- No parts of
g's return value should be checked.
- If
-
bimapmust return a value of the same Bifunctor.
Profunctor
A value that implements the Profunctor specification must also implement the Functor specification.
p.promap(a => a, b => b)is equivalent top(identity)p.promap(a => f(g(a)), b => h(i(b)))is equivalent top.promap(f, i).promap(g, h)(composition)
promap method
promap :: Profunctor p => p b c ~> (a -> b, c -> d) -> p a dA value which has a Profunctor must provide a promap method.
The profunctor method takes two arguments:
c.promap(f, g)
-
fmust be a function which returns a value- If
fis not a function, the behaviour ofpromapis unspecified. fcan return any value.- No parts of
f's return value should be checked.
- If
-
gmust be a function which returns a value- If
gis not a function, the behaviour ofpromapis unspecified. gcan return any value.- No parts of
g's return value should be checked.
- If
-
promapmust return a value of the same Profunctor
Derivations
When creating data types which satisfy multiple algebras, authors may choose to implement certain methods then derive the remaining methods. Derivations:
-
equalsmay be derived fromlte:function(other) { return this.lte(other) && other.lte(this); }
-
mapmay be derived fromapandof:function(f) { return this.ap(this.of(f)); }
-
mapmay be derived fromchainandof:function(f) { return this.chain(a => this.of(f(a))); }
-
mapmay be derived frombimap:function(f) { return this.bimap(a => a, f); }
-
mapmay be derived frompromap:function(f) { return this.promap(a => a, f); }
-
function(m) { return m.chain(f => this.map(f)); }
-
reducemay be derived as follows:function(f, acc) { function Const(value) { this.value = value; } Const.of = function(_) { return new Const(acc); }; Const.prototype.map = function(_) { return this; }; Const.prototype.ap = function(b) { return new Const(f(b.value, this.value)); }; return this.traverse(x => new Const(x), Const.of).value; }
-
mapmay be derived as follows:function(f) { function Id(value) { this.value = value; } Id.of = function(x) { return new Id(x); }; Id.prototype.map = function(f) { return new Id(f(this.value)); }; Id.prototype.ap = function(b) { return new Id(this.value(b.value)); }; return this.traverse(x => Id.of(f(x)), Id.of).value; }
-
filtermay be derived fromof,chain, andzero:function(pred) { var F = this.constructor; return this.chain(x => pred(x) ? F.of(x) : F.zero()); }
-
filtermay be derived fromconcat,of,zero, andreduce:function(pred) { var F = this.constructor; return this.reduce((f, x) => pred(x) ? f.concat(F.of(x)) : f, F.zero()); }
If a data type provides a method which could be derived, its behaviour must be equivalent to that of the derivation (or derivations).
Notes
- 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
Identitycontainer which implements many of the methods is provided by sanctuary-identity.
Alternatives
There also exists Static Land Specification with exactly the same ideas as Fantasy Land but based on static methods instead of instance methods.

