fantasyland / fantasy-land
- пятница, 16 сентября 2016 г. в 03:16:23
JavaScript
Specification for interoperability of common algebraic structures in JavaScript
(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.
In order to add compatibility with Fantasy Land to your library,
you need to add methods that you want to support with fantasy-land/
prefix.
For example if a type implements Functors' map
, you need to add fantasy-land/map
method to it.
The code may look something like this:
MyType.prototype['fantasy-land/map'] = MyType.prototype.map
It's not required to add unprefixed methods (e.g. MyType.prototype.map
)
for compatibility with Fantasy Land, but you're free to do so of course.
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] = MyType.prototype.map
// ...
var foo = bar[fl.map](x => x + 1)
a.equals(a) === true
(reflexivity)a.equals(b) === b.equals(a)
(symmetry)a.equals(b)
and b.equals(c)
, then a.equals(c)
(transitivity)equals
methodequals :: Setoid a => a ~> a -> Boolean
A value which has a Setoid must provide an equals
method. The
equals
method takes one argument:
a.equals(b)
b
must be a value of the same Setoid
b
is not the same Setoid, behaviour of equals
is
unspecified (returning false
is recommended).equals
must return a boolean (true
or false
).
a.concat(b).concat(c)
is equivalent to a.concat(b.concat(c))
(associativity)concat
methodconcat :: Semigroup a => a ~> a -> a
A value which has a Semigroup must provide a concat
method. The
concat
method takes one argument:
s.concat(b)
b
must be a value of the same Semigroup
b
is not the same semigroup, behaviour of concat
is
unspecified.concat
must 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 to m
(right identity)m.empty().concat(m)
is equivalent to m
(left identity)empty
methodempty :: Monoid m => () -> m
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()
empty
must return a value of the same Monoidu.map(a => a)
is equivalent to u
(identity)u.map(x => f(g(x)))
is equivalent to u.map(g).map(f)
(composition)map
methodmap :: Functor f => f a ~> (a -> b) -> f b
A value which has a Functor must provide a map
method. The map
method takes one argument:
u.map(f)
f
must be a function,
f
is not a function, the behaviour of map
is
unspecified.f
can return any value.map
must return a value of the same Functor
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 to v.ap(u).ap(a)
(composition)ap
methodap :: Apply f => f a ~> f (a -> b) -> f b
A value which has an Apply must provide an ap
method. The ap
method takes one argument:
a.ap(b)
b
must be an Apply of a function,
b
does not represent a function, the behaviour of ap
is
unspecified.a
must be an Apply of any value
ap
must apply the function in Apply b
to the value in
Apply a
A value that implements the Applicative specification must also implement the Apply specification.
v.ap(a.of(x => x))
is equivalent to v
(identity)a.of(x).ap(a.of(f))
is equivalent to a.of(f(x))
(homomorphism)a.of(y).ap(u)
is equivalent to u.ap(a.of(f => f(y)))
(interchange)of
methodof :: Applicative f => a -> f a
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)
of
must provide a value of the same Applicative
b
should be checkedu.reduce
is equivalent to u.reduce((acc, x) => acc.concat([x]), []).reduce
reduce
methodreduce :: Foldable f => f a ~> (b -> a -> b) -> b -> b
A value which has a Foldable must provide a reduce
method. The reduce
method takes two arguments:
u.reduce(f, x)
f
must be a binary function
f
is not a function, the behaviour of reduce
is unspecified.f
must be the same type as x
.f
must return a value of the same type as x
x
is the initial accumulator value for the reduction
A value that implements the Traversable specification must also implement the Functor and Foldable specifications.
t(u.traverse(x => x, F.of))
is equivalent to u.traverse(t, G.of)
for any t
such that t(a).map(f)
is equivalent to t(a.map(f))
(naturality)
u.traverse(F.of, F.of)
is equivalent to F.of(u)
for any Applicative F
(identity)
u.traverse(x => new Compose(x), Compose.of)
is equivalent to
new Compose(u.traverse(x => x, F.of).map(x => x.traverse(x => x, G.of)))
for Compose
defined below and any Applicatives F
and G
(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
methodtraverse :: Apply f, Traversable t => t a ~> ((a -> f b), (c -> f c)) -> f (t b)
A value which has a Traversable must provide a traverse
method. The traverse
method takes two arguments:
u.traverse(f, of)
f
must be a function which returns a value
f
is not a function, the behaviour of traverse
is
unspecified.f
must return a value of an Applicativeof
must be the of
method of the Applicative that f
returns
traverse
must return a value of the same Applicative that f
returnsA value that implements the Chain specification must also implement the Apply specification.
m.chain(f).chain(g)
is equivalent to m.chain(x => f(x).chain(g))
(associativity)chain
methodchain :: Chain m => m a ~> (a -> m b) -> m b
A value which has a Chain must provide a chain
method. The chain
method takes one argument:
m.chain(f)
f
must be a function which returns a value
f
is not a function, the behaviour of chain
is
unspecified.f
must return a value of the same Chainchain
must return a value of the same Chain
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)m.chainRec(f, i)
must be at most a constant multiple of the stack usage of f
itself.chainRec
methodchainRec :: ChainRec m => ((a -> c) -> (b -> c) -> a -> m c) -> a -> m b
A Type which has a ChainRec must provide a chainRec
method on itself
or its constructor
object. The chainRec
method takes two arguments:
a.chainRec(f, i)
a.constructor.chainRec(f, i)
f
must be a function which returns a value
f
is not a function, the behaviour of chainRec
is unspecified.f
takes three arguments next
, done
, value
next
is a function which takes one argument of same type as i
and can return any valuedone
is a function which takes one argument and returns the same type as the return value of next
value
is some value of the same type as i
f
must return a value of the same ChainRec which contains a value returned from either done
or next
chainRec
must return a value of the same ChainRec which contains a value of same type as argument of done
A value that implements the Monad specification must also implement the Applicative and Chain specifications.
m.of(a).chain(f)
is equivalent to f(a)
(left identity)m.chain(m.of)
is equivalent to m
(right identity)w.extend(g).extend(f)
is equivalent to w.extend(_w => f(_w.extend(g)))
extend
methodextend :: Extend w => w a ~> (w a -> b) -> w b
An Extend must provide an extend
method. The extend
method takes one argument:
w.extend(f)
f
must be a function which returns a value
f
is not a function, the behaviour of extend
is
unspecified.f
must return a value of type v
, for some variable v
contained in w
.extend
must 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 to w
w.extend(f).extract()
is equivalent to f(w)
w.extend(f)
is equivalent to w.extend(x => x).map(f)
extract
methodextract :: Comonad w => w a ~> () -> a
A value which has a Comonad must provide an extract
method on itself.
The extract
method takes no arguments:
c.extract()
extract
must return a value of type v
, for some variable v
contained in w
.
v
must have the same type that f
returns in extend
.A value that implements the Bifunctor specification must also implement the Functor specification.
p.bimap(a => a, b => b)
is equivalent to p
(identity)p.bimap(a => f(g(a)), b => h(i(b))
is equivalent to p.bimap(g, i).bimap(f, h)
(composition)bimap
methodbimap :: Bifunctor f => f a c ~> (a -> b) -> (c -> d) -> f b d
A value which has a Bifunctor must provide a bimap
method. The bimap
method takes two arguments:
c.bimap(f, g)
f
must be a function which returns a value
f
is not a function, the behaviour of bimap
is unspecified.f
can return any value.g
must be a function which returns a value
g
is not a function, the behaviour of bimap
is unspecified.g
can return any value.bimap
must return a value of the same Bifunctor.
A value that implements the Profunctor specification must also implement the Functor specification.
p.promap(a => a, b => b)
is equivalent to p
(identity)p.promap(a => f(g(a)), b => h(i(b)))
is equivalent to p.promap(f, i).promap(g, h)
(composition)promap
methodpromap :: Profunctor p => p b c ~> (a -> b) -> (c -> d) -> p a d
A value which has a Profunctor must provide a promap
method.
The profunctor
method takes two arguments:
c.promap(f, g)
f
must be a function which returns a value
f
is not a function, the behaviour of promap
is unspecified.f
can return any value.g
must be a function which returns a value
g
is not a function, the behaviour of promap
is unspecified.g
can return any value.promap
must return a value of the same Profunctor
When creating data types which satisfy multiple algebras, authors may choose to implement certain methods then derive the remaining methods. Derivations:
map
may be derived from ap
and of
:
function(f) { return this.ap(this.of(f)); }
map
may be derived from chain
and of
:
function(f) { return this.chain(a => this.of(f(a))); }
map
may be derived from bimap
:
function(f) { return this.bimap(a => a, f); }
map
may be derived from promap
:
function(f) { return this.promap(a => a, f); }
function(m) { return m.chain(f => this.map(f)); }
reduce
may 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;
}
map
may 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;
}
If a data type provides a method which could be derived, its behaviour must be equivalent to that of the derivation (or derivations).
Id
container which implements all methods is provided in
id.js
.There also exists Static Land Specification with the exactly same ideas as Fantasy Land but based on static methods instead of instance methods.