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 :: t
can be read as: "the expressione
is a member of typet
".true :: Boolean
- "true
is a member of typeBoolean
".42 :: Integer, Number
- "42
is a member of theInteger
andNumber
types".
- New types can be created via type constructors.
- Type constructors can take zero or more type arguments.
Array
is a type constructor which takes one type argument.Array String
is 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 String
is a type satisfied by functions which take aString
and return anArray String
.String -> Array String -> Array String
is a type satisfied by functions which take aString
and return a function which takes anArray String
and returns anArray String
.(String, Array String) -> Array String
is a type satisfied by functions which take aString
and anArray String
as arguments and return anArray String
.() -> Number
is 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 -> a
is a type satisfied by methods on Objects of typea
which take a typea
as an argument and return a value of typea
.
=>
(fat arrow) Expresses constraints on type variables.- In
a ~> a -> a
(see squiggly arrow above),a
can be of any type.Semigroup a => a ~> a -> a
adds a constraint such that the typea
must now satisfy theSemigroup
typeclass. To satisfy a typeclass means to lawfully implement all functions/methods specified by that typeclass.
- In
For example:
fantasy-land/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
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['fantasy-land/equals'](a) === true
(reflexivity)a['fantasy-land/equals'](b) === b['fantasy-land/equals'](a)
(symmetry)- If
a['fantasy-land/equals'](b)
andb['fantasy-land/equals'](c)
, thena['fantasy-land/equals'](c)
(transitivity)
fantasy-land/equals
method
fantasy-land/equals :: Setoid a => a ~> a -> Boolean
A value which has a Setoid must provide a fantasy-land/equals
method. The
fantasy-land/equals
method takes one argument:
a['fantasy-land/equals'](b)
-
b
must be a value of the same Setoid- If
b
is not the same Setoid, behaviour offantasy-land/equals
is unspecified (returningfalse
is recommended).
- If
-
fantasy-land/equals
must return a boolean (true
orfalse
).
Ord
A value that implements the Ord specification must also implement the Setoid specification.
a['fantasy-land/lte'](b)
orb['fantasy-land/lte'](a)
(totality)- If
a['fantasy-land/lte'](b)
andb['fantasy-land/lte'](a)
, thena['fantasy-land/equals'](b)
(antisymmetry) - If
a['fantasy-land/lte'](b)
andb['fantasy-land/lte'](c)
, thena['fantasy-land/lte'](c)
(transitivity)
fantasy-land/lte
method
fantasy-land/lte :: Ord a => a ~> a -> Boolean
A value which has an Ord must provide a fantasy-land/lte
method. The
fantasy-land/lte
method takes one argument:
a['fantasy-land/lte'](b)
-
b
must be a value of the same Ord- If
b
is not the same Ord, behaviour offantasy-land/lte
is unspecified (returningfalse
is recommended).
- If
-
fantasy-land/lte
must return a boolean (true
orfalse
).
Semigroupoid
a['fantasy-land/compose'](b)['fantasy-land/compose'](c) === a['fantasy-land/compose'](b['fantasy-land/compose'](c))
(associativity)
fantasy-land/compose
method
fantasy-land/compose :: Semigroupoid c => c i j ~> c j k -> c i k
A value which has a Semigroupoid must provide a fantasy-land/compose
method. The
fantasy-land/compose
method takes one argument:
a['fantasy-land/compose'](b)
-
b
must be a value of the same Semigroupoid- If
b
is not the same semigroupoid, behaviour offantasy-land/compose
is unspecified.
- If
-
fantasy-land/compose
must return a value of the same Semigroupoid.
Category
A value that implements the Category specification must also implement the Semigroupoid specification.
a['fantasy-land/compose'](C['fantasy-land/id']())
is equivalent toa
(right identity)C['fantasy-land/id']()['fantasy-land/compose'](a)
is equivalent toa
(left identity)
fantasy-land/id
method
fantasy-land/id :: Category c => () -> c a a
A value which has a Category must provide a fantasy-land/id
function on its
type representative:
C['fantasy-land/id']()
Given a value c
, one can access its type representative via the
constructor
property:
c.constructor['fantasy-land/id']()
fantasy-land/id
must return a value of the same Category
Semigroup
a['fantasy-land/concat'](b)['fantasy-land/concat'](c)
is equivalent toa['fantasy-land/concat'](b['fantasy-land/concat'](c))
(associativity)
fantasy-land/concat
method
fantasy-land/concat :: Semigroup a => a ~> a -> a
A value which has a Semigroup must provide a fantasy-land/concat
method. The
fantasy-land/concat
method takes one argument:
s['fantasy-land/concat'](b)
-
b
must be a value of the same Semigroup- If
b
is not the same semigroup, behaviour offantasy-land/concat
is unspecified.
- If
-
fantasy-land/concat
must return a value of the same Semigroup.
Monoid
A value that implements the Monoid specification must also implement the Semigroup specification.
m['fantasy-land/concat'](M['fantasy-land/empty']())
is equivalent tom
(right identity)M['fantasy-land/empty']()['fantasy-land/concat'](m)
is equivalent tom
(left identity)
fantasy-land/empty
method
fantasy-land/empty :: Monoid m => () -> m
A value which has a Monoid must provide a fantasy-land/empty
function on its
type representative:
M['fantasy-land/empty']()
Given a value m
, one can access its type representative via the
constructor
property:
m.constructor['fantasy-land/empty']()
fantasy-land/empty
must return a value of the same Monoid
Group
A value that implements the Group specification must also implement the Monoid specification.
g['fantasy-land/concat'](g['fantasy-land/invert']())
is equivalent tog.constructor['fantasy-land/empty']()
(right inverse)g['fantasy-land/invert']()['fantasy-land/concat'](g)
is equivalent tog.constructor['fantasy-land/empty']()
(left inverse)
fantasy-land/invert
method
fantasy-land/invert :: Group g => g ~> () -> g
A value which has a Group must provide a fantasy-land/invert
method. The
fantasy-land/invert
method takes no arguments:
g['fantasy-land/invert']()
fantasy-land/invert
must return a value of the same Group.
Filterable
v['fantasy-land/filter'](x => p(x) && q(x))
is equivalent tov['fantasy-land/filter'](p)['fantasy-land/filter'](q)
(distributivity)v['fantasy-land/filter'](x => true)
is equivalent tov
(identity)v['fantasy-land/filter'](x => false)
is equivalent tow['fantasy-land/filter'](x => false)
ifv
andw
are values of the same Filterable (annihilation)
fantasy-land/filter
method
fantasy-land/filter :: Filterable f => f a ~> (a -> Boolean) -> f a
A value which has a Filterable must provide a fantasy-land/filter
method. The fantasy-land/filter
method takes one argument:
v['fantasy-land/filter'](p)
-
p
must be a function.- If
p
is not a function, the behaviour offantasy-land/filter
is unspecified. p
must return eithertrue
orfalse
. If it returns any other value, the behaviour offantasy-land/filter
is unspecified.
- If
-
fantasy-land/filter
must return a value of the same Filterable.
Functor
u['fantasy-land/map'](a => a)
is equivalent tou
(identity)u['fantasy-land/map'](x => f(g(x)))
is equivalent tou['fantasy-land/map'](g)['fantasy-land/map'](f)
(composition)
fantasy-land/map
method
fantasy-land/map :: Functor f => f a ~> (a -> b) -> f b
A value which has a Functor must provide a fantasy-land/map
method. The fantasy-land/map
method takes one argument:
u['fantasy-land/map'](f)
-
f
must be a function,- If
f
is not a function, the behaviour offantasy-land/map
is unspecified. f
can return any value.- No parts of
f
's return value should be checked.
- If
-
fantasy-land/map
must return a value of the same Functor
Contravariant
u['fantasy-land/contramap'](a => a)
is equivalent tou
(identity)u['fantasy-land/contramap'](x => f(g(x)))
is equivalent tou['fantasy-land/contramap'](f)['fantasy-land/contramap'](g)
(composition)
fantasy-land/contramap
method
fantasy-land/contramap :: Contravariant f => f a ~> (b -> a) -> f b
A value which has a Contravariant must provide a fantasy-land/contramap
method. The
fantasy-land/contramap
method takes one argument:
u['fantasy-land/contramap'](f)
-
f
must be a function,- If
f
is not a function, the behaviour of
- If