fantasy-land
Specification for interoperability of common algebraic structures in JavaScript
Top Related Projects
:ram: Practical functional Javascript
A modern JavaScript utility library delivering modularity, performance, & extras.
:see_no_evil: Refuge from unsafe JavaScript
[not actively maintained!] A standard library for functional programming in JavaScript
🦋 Fantasy Land compliant (monadic) alternative to Promises
monet.js - Monadic types library for JavaScript
Quick Overview
Fantasy Land is a specification for interoperability of common algebraic structures in JavaScript. It defines a set of algebraic laws that various data types can implement, allowing developers to write more generic and composable code. The project aims to improve code reuse and provide a common vocabulary for functional programming concepts in JavaScript.
Pros
- Promotes a standardized approach to functional programming in JavaScript
- Enables better interoperability between different libraries and frameworks
- Encourages the creation of more composable and reusable code
- Provides a clear set of laws and specifications for implementing algebraic structures
Cons
- Can be challenging for developers not familiar with functional programming concepts
- May introduce additional complexity to codebases
- Limited adoption compared to more mainstream JavaScript paradigms
- Requires a significant shift in thinking for developers used to object-oriented programming
Code Examples
- Implementing a Functor:
const MyFunctor = {
map: function(f) {
return MyFunctor.of(f(this.value));
},
of: function(x) {
return { value: x, map: MyFunctor.map };
}
};
const result = MyFunctor.of(5).map(x => x * 2);
console.log(result.value); // Output: 10
- Implementing a Monoid:
const Sum = {
empty: () => 0,
concat: (a, b) => a + b
};
const numbers = [1, 2, 3, 4, 5];
const total = numbers.reduce(Sum.concat, Sum.empty());
console.log(total); // Output: 15
- Implementing an Applicative:
const Maybe = {
of: x => ({ value: x, isNothing: false }),
nothing: { isNothing: true },
ap: function(f) {
return this.isNothing ? Maybe.nothing : Maybe.of(f.value(this.value));
}
};
const add = x => y => x + y;
const result = Maybe.of(add)
.ap(Maybe.of(2))
.ap(Maybe.of(3));
console.log(result.value); // Output: 5
Getting Started
To use Fantasy Land in your project:
-
Install the package:
npm install fantasy-land
-
Import the desired algebraic structures:
const fl = require('fantasy-land');
-
Implement the required methods for your data types:
const MyType = { [fl.map]: function(f) { // Implement map functionality }, [fl.of]: function(x) { // Implement of functionality } };
-
Use your implemented types with libraries that support Fantasy Land:
const result = someLibrary.someFunction(MyType.of(5));
Competitor Comparisons
:ram: Practical functional Javascript
Pros of Ramda
- Provides a comprehensive set of functional programming utilities
- Ready-to-use functions for practical application development
- Extensive documentation and examples for each function
Cons of Ramda
- Larger library size compared to Fantasy Land
- May include functions not strictly adhering to algebraic structures
- Steeper learning curve for developers new to functional programming
Code Comparison
Fantasy Land (specification):
// Functor specification
const f = a => a + 1;
const g = a => a * 2;
x.map(x => f(g(x))) === x.map(g).map(f)
Ramda (implementation):
const R = require('ramda');
const f = a => a + 1;
const g = a => a * 2;
R.compose(R.map(f), R.map(g)) === R.map(R.compose(f, g))
Key Differences
- Fantasy Land focuses on defining algebraic structures and laws
- Ramda provides concrete implementations of functional programming concepts
- Fantasy Land is a specification, while Ramda is a utility library
- Ramda offers more immediate practical value for application development
- Fantasy Land ensures mathematical correctness and interoperability
Use Cases
- Use Fantasy Land when defining or implementing new functional programming libraries
- Choose Ramda for building applications with functional programming techniques
- Combine both: use Ramda while adhering to Fantasy Land specifications for maximum benefit
A modern JavaScript utility library delivering modularity, performance, & extras.
Pros of Lodash
- Extensive utility library with a wide range of functions for common programming tasks
- Well-documented and widely adopted in the JavaScript community
- Provides performance optimizations for many operations
Cons of Lodash
- Large bundle size if importing the entire library
- Some functions may be redundant with modern JavaScript features
- Less focus on functional programming concepts compared to Fantasy Land
Code Comparison
Fantasy Land (specification example):
const Functor = {
map: (f) => Functor.of(f(x))
}
Lodash (utility function example):
_.map([1, 2, 3], (n) => n * 2);
// => [2, 4, 6]
Key Differences
- Fantasy Land is a specification for algebraic structures in JavaScript, while Lodash is a utility library
- Fantasy Land focuses on functional programming concepts, whereas Lodash provides general-purpose utility functions
- Lodash offers immediate practical solutions, while Fantasy Land defines interfaces for creating more abstract, composable code
Use Cases
- Use Fantasy Land when implementing functional programming patterns or creating libraries that adhere to algebraic structures
- Choose Lodash for quick access to utility functions and performance-optimized operations in everyday JavaScript development
:see_no_evil: Refuge from unsafe JavaScript
Pros of Sanctuary
- Provides a complete functional programming library with practical implementations
- Offers extensive documentation and examples for easier adoption
- Includes runtime type checking for enhanced safety and debugging
Cons of Sanctuary
- Larger codebase and API surface area, potentially steeper learning curve
- May have performance overhead due to runtime type checking
- Less flexibility in choosing individual components compared to Fantasy Land
Code Comparison
Fantasy Land (specification):
// No direct implementation, only interface definitions
const Functor = {
map: (f) => {}
};
Sanctuary (implementation):
const S = require('sanctuary');
// Practical use of functors
const result = S.map(S.add(1), [1, 2, 3]);
console.log(result); // [2, 3, 4]
Summary
Fantasy Land is a specification for algebraic structures in JavaScript, providing a set of standardized interfaces for functional programming concepts. It focuses on defining a common vocabulary and set of laws for these structures.
Sanctuary, on the other hand, is a full-fledged functional programming library that implements many of the algebraic structures defined in Fantasy Land. It provides practical tools and functions for developers to use in their projects, with a strong emphasis on type safety and documentation.
While Fantasy Land offers a foundation for creating interoperable functional programming libraries, Sanctuary provides a ready-to-use solution with additional features like runtime type checking. The choice between them depends on whether you need a specification to build upon or a complete library to use in your projects.
[not actively maintained!] A standard library for functional programming in JavaScript
Pros of Folktale
- Provides concrete implementations of algebraic data types and functional programming concepts
- Offers a more comprehensive library with practical utilities and data structures
- Includes detailed documentation and examples for easier adoption
Cons of Folktale
- Less focused on establishing a standardized specification for functional programming
- May have a steeper learning curve due to its broader scope
- Potentially larger bundle size when including the entire library
Code Comparison
Fantasy Land (specification):
// Example of a Functor specification
const myFunctor = {
map: (f) => myFunctor
};
Folktale (implementation):
const { task } = require('folktale/concurrency/task');
const myTask = task((resolver) => {
resolver.resolve('Hello, World!');
}).map((value) => value.toUpperCase());
Summary
Fantasy Land focuses on defining a specification for algebraic structures in JavaScript, while Folktale provides concrete implementations and utilities based on functional programming principles. Fantasy Land is more suitable for those looking to understand and implement the core concepts, whereas Folktale offers a ready-to-use library with practical applications. The choice between them depends on whether you need a specification to follow or a full-featured functional programming toolkit.
🦋 Fantasy Land compliant (monadic) alternative to Promises
Pros of Fluture
- Provides a concrete implementation of Future monads, offering practical utility
- Includes a rich set of methods for working with asynchronous operations
- Offers better performance compared to other Future implementations
Cons of Fluture
- More complex and specific than the abstract Fantasy Land specification
- Requires learning a new API and paradigm for handling asynchronous operations
- May have a steeper learning curve for developers unfamiliar with functional programming concepts
Code Comparison
Fantasy Land (specification):
// No direct implementation, only specifications
const MyType = daggy.tagged('MyType', ['value'])
MyType.prototype.map = function (f) {
return MyType(f(this.value))
}
Fluture (implementation):
import {Future} from 'fluture'
const myFuture = Future((reject, resolve) => {
setTimeout(() => resolve('Hello, World!'), 1000)
})
myFuture.map(x => x.toUpperCase()).fork(console.error, console.log)
Summary
Fantasy Land provides a set of specifications for algebraic structures in JavaScript, while Fluture offers a concrete implementation of Futures with a focus on asynchronous operations. Fantasy Land is more abstract and broadly applicable, whereas Fluture provides practical tools for working with asynchronous code in a functional style.
monet.js - Monadic types library for JavaScript
Pros of monet.js
- Provides concrete implementations of functional programming concepts
- Offers a more comprehensive set of data types and utilities
- Easier to directly use in JavaScript projects
Cons of monet.js
- Less flexible than Fantasy Land's specification approach
- May have a steeper learning curve for beginners
- Potentially larger bundle size due to included implementations
Code Comparison
Fantasy Land (specification):
// Example of a Functor specification
const myFunctor = {
map: (f) => /* implementation */
};
monet.js (implementation):
// Example of a Functor implementation
const myFunctor = Monet.Maybe.Some(5)
.map(x => x * 2);
Summary
Fantasy Land is a specification for algebraic structures in JavaScript, providing a set of rules for implementing functional programming concepts. It focuses on interoperability and standardization across libraries.
monet.js, on the other hand, is a concrete implementation of functional programming tools and data types. It offers ready-to-use structures like Maybe, Either, and IO, making it more immediately applicable in projects.
While Fantasy Land allows for greater flexibility and customization, monet.js provides a more straightforward path to using functional programming concepts in JavaScript. The choice between them depends on whether you need a specification to build upon or a ready-made set of functional tools.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
Fantasy Land Specification
(aka "Algebraic JavaScript Specification")
This project specifies interoperability of common algebraic structures:
Setoid Semigroupoid Semigroup Foldable Functor Contravariant Filterable (equals) (compose) (concat) (reduce) (map) (contramap) (filter) | | | \ / | | | | \ | | | \ / | | | | \ | | | \ / | | | | \ | | | \ / | | | | \ | | | \ / | | | | \ Ord Category Monoid Traversable | | | | \ (lte) (id) (empty) (traverse) / | | \ \ | / | | \ \ | / / \ \ \ | Profunctor / \ Bifunctor \ | (promap) / \ (bimap) \ | / \ \ Group / \ \ (invert) Alt Apply Extend (alt) (ap) (extend) / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ Plus Applicative Chain Comonad (zero) (of) (chain) (extract) \ / \ / \ \ / \ / \ \ / \ / \ \ / \ / \ \ / \ / \ Alternative Monad ChainRec (chainRec)
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 offantasy-land/contramap
is unspecified. f
can return any value.- No parts of
f
's return value should be checked.
- If
-
fantasy-land/contramap
must return a value of the same Contravariant
Apply
A value that implements the Apply specification must also implement the Functor specification.
v['fantasy-land/ap'](u['fantasy-land/ap'](a['fantasy-land/map'](f => g => x => f(g(x)))))
is equivalent tov['fantasy-land/ap'](u)['fantasy-land/ap'](a)
(composition)
fantasy-land/ap
method
fantasy-land/ap :: Apply f => f a ~> f (a -> b) -> f b
A value which has an Apply must provide a fantasy-land/ap
method. The fantasy-land/ap
method takes one argument:
a['fantasy-land/ap'](b)
-
b
must be an Apply of a function- If
b
does not represent a function, the behaviour offantasy-land/ap
is unspecified. b
must be same Apply asa
.
- If
-
a
must be an Apply of any value -
fantasy-land/ap
must apply the function in Applyb
to the value in Applya
- No parts of return value of that function should be checked.
-
The
Apply
returned byfantasy-land/ap
must be the same asa
andb
Applicative
A value that implements the Applicative specification must also implement the Apply specification.
v['fantasy-land/ap'](A['fantasy-land/of'](x => x))
is equivalent tov
(identity)A['fantasy-land/of'](x)['fantasy-land/ap'](A['fantasy-land/of'](f))
is equivalent toA['fantasy-land/of'](f(x))
(homomorphism)A['fantasy-land/of'](y)['fantasy-land/ap'](u)
is equivalent tou['fantasy-land/ap'](A['fantasy-land/of'](f => f(y)))
(interchange)
fantasy-land/of
method
fantasy-land/of :: Applicative f => a -> f a
A value which has an Applicative must provide a fantasy-land/of
function on its
type representative. The fantasy-land/of
function takes
one argument:
F['fantasy-land/of'](a)
Given a value f
, one can access its type representative via the
constructor
property:
f.constructor['fantasy-land/of'](a)
-
fantasy-land/of
must provide a value of the same Applicative- No parts of
a
should be checked
- No parts of
Alt
A value that implements the Alt specification must also implement the Functor specification.
a['fantasy-land/alt'](b)['fantasy-land/alt'](c)
is equivalent toa['fantasy-land/alt'](b['fantasy-land/alt'](c))
(associativity)a['fantasy-land/alt'](b)['fantasy-land/map'](f)
is equivalent toa['fantasy-land/map'](f)['fantasy-land/alt'](b['fantasy-land/map'](f))
(distributivity)
fantasy-land/alt
method
fantasy-land/alt :: Alt f => f a ~> f a -> f a
A value which has a Alt must provide a fantasy-land/alt
method. The
fantasy-land/alt
method takes one argument:
a['fantasy-land/alt'](b)
-
b
must be a value of the same Alt- If
b
is not the same Alt, behaviour offantasy-land/alt
is unspecified. a
andb
can contain any value of same type.- No parts of
a
's andb
's containing value should be checked.
- If
-
fantasy-land/alt
must return a value of the same Alt.
Plus
A value that implements the Plus specification must also implement the Alt specification.
x['fantasy-land/alt'](A['fantasy-land/zero']())
is equivalent tox
(right identity)A['fantasy-land/zero']()['fantasy-land/alt'](x)
is equivalent tox
(left identity)A['fantasy-land/zero']()['fantasy-land/map'](f)
is equivalent toA['fantasy-land/zero']()
(annihilation)
fantasy-land/zero
method
fantasy-land/zero :: Plus f => () -> f a
A value which has a Plus must provide a fantasy-land/zero
function on its
type representative:
A['fantasy-land/zero']()
Given a value x
, one can access its type representative via the
constructor
property:
x.constructor['fantasy-land/zero']()
fantasy-land/zero
must return a value of the same Plus
Alternative
A value that implements the Alternative specification must also implement the Applicative and Plus specifications.
x['fantasy-land/ap'](f['fantasy-land/alt'](g))
is equivalent tox['fantasy-land/ap'](f)['fantasy-land/alt'](x['fantasy-land/ap'](g))
(distributivity)x['fantasy-land/ap'](A['fantasy-land/zero']())
is equivalent toA['fantasy-land/zero']()
(annihilation)
Foldable
u['fantasy-land/reduce']
is equivalent tou['fantasy-land/reduce']((acc, x) => acc.concat([x]), []).reduce
fantasy-land/reduce
method
fantasy-land/reduce :: Foldable f => f a ~> ((b, a) -> b, b) -> b
A value which has a Foldable must provide a fantasy-land/reduce
method. The fantasy-land/reduce
method takes two arguments:
u['fantasy-land/reduce'](f, x)
-
f
must be a binary function- if
f
is not a function, the behaviour offantasy-land/reduce
is unspecified. - The first argument to
f
must be the same type asx
. f
must return a value of the same type asx
.- No parts of
f
's return value should be checked.
- if
-
x
is the initial accumulator value for the reduction- No parts of
x
should be checked.
- No parts of
Traversable
A value that implements the Traversable specification must also implement the Functor and Foldable specifications.
-
t(u['fantasy-land/traverse'](F, x => x))
is equivalent tou['fantasy-land/traverse'](G, t)
for anyt
such thatt(a)['fantasy-land/map'](f)
is equivalent tot(a['fantasy-land/map'](f))
(naturality) -
u['fantasy-land/traverse'](F, F['fantasy-land/of'])
is equivalent toF['fantasy-land/of'](u)
for any ApplicativeF
(identity) -
u['fantasy-land/traverse'](Compose, x => new Compose(x))
is equivalent tonew Compose(u['fantasy-land/traverse'](F, x => x)['fantasy-land/map'](x => x['fantasy-land/traverse'](G, x => x)))
forCompose
defined below and any ApplicativesF
andG
(composition)
function Compose(c) {
this.c = c;
}
Compose['fantasy-land/of'] = function(x) {
return new Compose(F['fantasy-land/of'](G['fantasy-land/of'](x)));
};
Compose.prototype['fantasy-land/ap'] = function(f) {
return new Compose(this.c['fantasy-land/ap'](f.c['fantasy-land/map'](u => y => y['fantasy-land/ap'](u))));
};
Compose.prototype['fantasy-land/map'] = function(f) {
return new Compose(this.c['fantasy-land/map'](y => y['fantasy-land/map'](f)));
};
fantasy-land/traverse
method
fantasy-land/traverse :: Applicative f, Traversable t => t a ~> (TypeRep f, a -> f b) -> f (t b)
A value which has a Traversable must provide a fantasy-land/traverse
method. The fantasy-land/traverse
method takes two arguments:
u['fantasy-land/traverse'](A, f)
-
A
must be the type representative of an Applicative. -
f
must be a function which returns a value- If
f
is not a function, the behaviour offantasy-land/traverse
is unspecified. f
must return a value of the type represented byA
.
- If
-
fantasy-land/traverse
must return a value of the type represented byA
.
Chain
A value that implements the Chain specification must also implement the Apply specification.
m['fantasy-land/chain'](f)['fantasy-land/chain'](g)
is equivalent tom['fantasy-land/chain'](x => f(x)['fantasy-land/chain'](g))
(associativity)
fantasy-land/chain
method
fantasy-land/chain :: Chain m => m a ~> (a -> m b) -> m b
A value which has a Chain must provide a fantasy-land/chain
method. The fantasy-land/chain
method takes one argument:
m['fantasy-land/chain'](f)
-
f
must be a function which returns a value- If
f
is not a function, the behaviour offantasy-land/chain
is unspecified. f
must return a value of the same Chain
- If
-
fantasy-land/chain
must return a value of the same Chain
ChainRec
A value that implements the ChainRec specification must also implement the Chain specification.
M['fantasy-land/chainRec']((next, done, v) => p(v) ? d(v)['fantasy-land/map'](done) : n(v)['fantasy-land/map'](next), i)
is equivalent to(function step(v) { return p(v) ? d(v) : n(v)['fantasy-land/chain'](step); }(i))
(equivalence)- Stack usage of
M['fantasy-land/chainRec'](f, i)
must be at most a constant multiple of the stack usage off
itself.
fantasy-land/chainRec
method
fantasy-land/chainRec :: ChainRec m => ((a -> c, b -> c, a) -> m c, a) -> m b
A Type which has a ChainRec must provide a fantasy-land/chainRec
function on its
type representative. The fantasy-land/chainRec
function
takes two arguments:
M['fantasy-land/chainRec'](f, i)
Given a value m
, one can access its type representative via the
constructor
property:
m.constructor['fantasy-land/chainRec'](f, i)
f
must be a function which returns a value- If
f
is not a function, the behaviour offantasy-land/chainRec
is unspecified. f
takes three argumentsnext
,done
,value
next
is a function which takes one argument of same type asi
and can return any valuedone
is a function which takes one argument and returns the same type as the return value ofnext
value
is some value of the same type asi
f
must return a value of the same ChainRec which contains a value returned from eitherdone
ornext
- If
fantasy-land/chainRec
must 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['fantasy-land/of'](a)['fantasy-land/chain'](f)
is equivalent tof(a)
(left identity)m['fantasy-land/chain'](M['fantasy-land/of'])
is equivalent tom
(right identity)
Extend
A value that implements the Extend specification must also implement the Functor specification.
w['fantasy-land/extend'](g)['fantasy-land/extend'](f)
is equivalent tow['fantasy-land/extend'](_w => f(_w['fantasy-land/extend'](g)))
fantasy-land/extend
method
fantasy-land/extend :: Extend w => w a ~> (w a -> b) -> w b
An Extend must provide a fantasy-land/extend
method. The fantasy-land/extend
method takes one argument:
w['fantasy-land/extend'](f)
-
f
must be a function which returns a value- If
f
is not a function, the behaviour offantasy-land/extend
is unspecified. f
must return a value of typev
, for some variablev
contained inw
.- No parts of
f
's return value should be checked.
- If
-
fantasy-land/extend
must return a value of the same Extend.
Comonad
A value that implements the Comonad specification must also implement the Extend specification.
w['fantasy-land/extend'](_w => _w['fantasy-land/extract']())
is equivalent tow
(left identity)w['fantasy-land/extend'](f)['fantasy-land/extract']()
is equivalent tof(w)
(right identity)
fantasy-land/extract
method
fantasy-land/extract :: Comonad w => w a ~> () -> a
A value which has a Comonad must provide a fantasy-land/extract
method on itself.
The fantasy-land/extract
method takes no arguments:
w['fantasy-land/extract']()
fantasy-land/extract
must return a value of typev
, for some variablev
contained inw
.v
must have the same type thatf
returns infantasy-land/extend
.
Bifunctor
A value that implements the Bifunctor specification must also implement the Functor specification.
p['fantasy-land/bimap'](a => a, b => b)
is equivalent top
(identity)p['fantasy-land/bimap'](a => f(g(a)), b => h(i(b)))
is equivalent top['fantasy-land/bimap'](g, i)['fantasy-land/bimap'](f, h)
(composition)
fantasy-land/bimap
method
fantasy-land/bimap :: Bifunctor f => f a c ~> (a -> b, c -> d) -> f b d
A value which has a Bifunctor must provide a fantasy-land/bimap
method. The fantasy-land/bimap
method takes two arguments:
c['fantasy-land/bimap'](f, g)
-
f
must be a function which returns a value- If
f
is not a function, the behaviour offantasy-land/bimap
is unspecified. f
can return any value.- No parts of
f
's return value should be checked.
- If
-
g
must be a function which returns a value- If
g
is not a function, the behaviour offantasy-land/bimap
is unspecified. g
can return any value.- No parts of
g
's return value should be checked.
- If
-
fantasy-land/bimap
must return a value of the same Bifunctor.
Profunctor
A value that implements the Profunctor specification must also implement the Functor specification.
p['fantasy-land/promap'](a => a, b => b)
is equivalent top
(identity)p['fantasy-land/promap'](a => f(g(a)), b => h(i(b)))
is equivalent top['fantasy-land/promap'](f, i)['fantasy-land/promap'](g, h)
(composition)
fantasy-land/promap
method
fantasy-land/promap :: Profunctor p => p b c ~> (a -> b, c -> d) -> p a d
A value which has a Profunctor must provide a fantasy-land/promap
method.
The fantasy-land/promap
method takes two arguments:
c['fantasy-land/promap'](f, g)
-
f
must be a function which returns a value- If
f
is not a function, the behaviour offantasy-land/promap
is unspecified. f
can return any value.- No parts of
f
's return value should be checked.
- If
-
g
must be a function which returns a value- If
g
is not a function, the behaviour offantasy-land/promap
is unspecified. g
can return any value.- No parts of
g
's return value should be checked.
- If
-
fantasy-land/promap
must 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:
-
fantasy-land/equals
may be derived fromfantasy-land/lte
:function equals(other) { return this['fantasy-land/lte'](other) && other['fantasy-land/lte'](this); }
-
fantasy-land/map
may be derived fromfantasy-land/ap
andfantasy-land/of
:function map(f) { return this['fantasy-land/ap'](this.constructor['fantasy-land/of'](f)); }
-
fantasy-land/map
may be derived fromfantasy-land/chain
andfantasy-land/of
:function map(f) { return this['fantasy-land/chain'](a => this.constructor['fantasy-land/of'](f(a))); }
-
fantasy-land/map
may be derived fromfantasy-land/bimap
:function map(f) { return this['fantasy-land/bimap'](a => a, f); }
-
fantasy-land/map
may be derived fromfantasy-land/promap
:function map(f) { return this['fantasy-land/promap'](a => a, f); }
-
fantasy-land/ap
may be derived fromfantasy-land/chain
:function ap(m) { return m['fantasy-land/chain'](f => this['fantasy-land/map'](f)); }
-
fantasy-land/reduce
may be derived as follows:function reduce(f, acc) { function Const(value) { this.value = value; } Const['fantasy-land/of'] = function(_) { return new Const(acc); }; Const.prototype['fantasy-land/map'] = function(_) { return this; }; Const.prototype['fantasy-land/ap'] = function(b) { return new Const(f(b.value, this.value)); }; return this['fantasy-land/traverse'](x => new Const(x), Const['fantasy-land/of']).value; }
-
fantasy-land/map
may be derived as follows:function map(f) { function Id(value) { this.value = value; } Id['fantasy-land/of'] = function(x) { return new Id(x); }; Id.prototype['fantasy-land/map'] = function(f) { return new Id(f(this.value)); }; Id.prototype['fantasy-land/ap'] = function(b) { return new Id(this.value(b.value)); }; return this['fantasy-land/traverse'](x => Id['fantasy-land/of'](f(x)), Id['fantasy-land/of']).value; }
-
fantasy-land/filter
may be derived fromfantasy-land/of
,fantasy-land/chain
, andfantasy-land/zero
:function filter(pred) { var F = this.constructor; return this['fantasy-land/chain'](x => pred(x) ? F['fantasy-land/of'](x) : F['fantasy-land/zero']()); }
-
fantasy-land/filter
may be derived fromfantasy-land/concat
,fantasy-land/of
,fantasy-land/zero
, andfantasy-land/reduce
:function filter(pred) { var F = this.constructor; return this['fantasy-land/reduce']((f, x) => pred(x) ? f['fantasy-land/concat'](F['fantasy-land/of'](x)) : f, F['fantasy-land/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
Identity
container 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.
Top Related Projects
:ram: Practical functional Javascript
A modern JavaScript utility library delivering modularity, performance, & extras.
:see_no_evil: Refuge from unsafe JavaScript
[not actively maintained!] A standard library for functional programming in JavaScript
🦋 Fantasy Land compliant (monadic) alternative to Promises
monet.js - Monadic types library for JavaScript
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot