(aka "Спецификация Алгебраический JavaScript")
Этот проект определяет взаимодействие общих алгебраических структур:
- Setoid
- Semigroup
- Monoid
- Functor
- Contravariant
- Apply
- Applicative
- Alt
- Plus
- Alternative
- Foldable
- Traversable
- Chain
- ChainRec
- Monad
- Extend
- Comonad
- Bifunctor
- Profunctor
Алгебра представляет собой набор значений, набор операторов, которые зависимы и должны подчиняться некоторым законам.
Каждая алгебра Fantasy Land – это отдельная спецификация. Алгебра может иметь зависимости от реализаций других алгебр.
- "значение" любые JavaScript значения, включая структуры определенные ниже.
- "эквивалент" - подходящее определение эквивалентности для заданного значения. Определение должно гарантировать, что эти два значения могут быть безопасно переставлены в программе, что подтверждает абстракцию. Например:
- Два списка эквивалентны, если они эквивалентны по всем показателям.
- Два обычных JavaScript объекта, представленные как словари, эквивалентны, если они эквивалентны по всем ключам.
- Два промиса эквивалентны, когда они возвращают эквивалентные значения.
- Две функции эквивалентны, если они дают эквивалентные результаты для эквивалентных входных данных.
Для тех типов данных, которые должны быть совместимы с Fantasy Land, значения должны иметь определенные свойства. Эти свойства имеют префикс fantasy-land/.
Например:
// MyType#fantasy-land/map :: MyType a ~> (a -> b) -> MyType b
MyType.prototype['fantasy-land/map'] = ...Далее в этом документе имена без префиксов используются только для уменьшения шума.
Для удобства вы можете использовать пакет fantasy-land:
var fl = require('fantasy-land')
// ...
MyType.prototype[fl.map] = ...
// ...
var foo = bar[fl.map](x => x + 1)Определенные виды поведения определяются с точки зрения представления типа. Другие поведения не требуют представления. Таким образом, определенные алгебры требуют тип для предоставления значения-уровня представления (с определенными свойствами). Тип Identity, например, может обеспечить Id в качестве своего представителя типа: Id :: TypeRep Identity.
Если тип предоставляет представителя типа, каждый представитель типа должен иметь свойство constructor, которое является ссылкой на представителя типа.
a.equals(a) === true(рефлексивность)a.equals(b) === b.equals(a)(симметрия)- Если
a.equals(b)иb.equals(c), тоa.equals(c)(транзитивность)
equals :: Setoid a => a ~> a -> BooleanЗначение Setoid должно предоставить метод equals. Метод equals принимает один аргумент:
a.equals(b)
-
bдолжен быть значением того же Setoid- Если
bне тот же Setoid, поведениеequalsне определено (рекомендуется возвращатьfalse)
- Если
-
equalsдолжен возвращать логическое значение (trueилиfalse)
a.concat(b).concat(c)эквивалентноa.concat(b.concat(c))(ассоциативность)
concat :: Semigroup a => a ~> a -> aЗначение Semigroup должно предоставить метод concat. Метод concat принимает один аргумент:
s.concat(b)
-
bдолжен быть значением того же Semigroup- Если
bне тот же Semigroup, поведениеconcatне определено
- Если
-
concatдолжен возвращать значение того же Semigroup
Значение, которое реализует спецификацию Monoid, должно также реализовать спецификацию Semigroup.
m.concat(M.empty())эквивалентноm(точный справа)M.empty().concat(m)эквивалентноm(точный слева)
empty :: Monoid m => () -> mЗначение Monoid должно предоставить метод empty в её представлении типа:
M.empty()
Получив значение m, можно получить представление типа через свойство constructor:
m.constructor.empty()
emptyдолжен возвращать значение того же Monoid
u.map(a => a)эквивалентноu(точный)u.map(x => f(g(x)))эквивалентноu.map(g).map(f)(композиция)
map :: Functor f => f a ~> (a -> b) -> f bЗначение Functor должно предоставить метод map. Метод map принимает один аргумент:
u.map(f)
-
fдолжен быть функцией- Если
fне функция, поведениеmapне определено fможет вернуть любое значение- Значения возвращенные
fпроверять не надо
- Если
-
mapдолжен возвращать значение того же Functor
u.contramap(a => a)эквивалентноu(точный)u.contramap(x => f(g(x)))эквивалентноu.contramap(f).contramap(g)(композиция)
contramap :: Contravariant f => f a ~> (b -> a) -> f bЗначение Contravariant должно предоставить метод contramap. Метод contramap принимает один аргумент:
u.contramap(f)
-
fдолжен быть функцией- Если
fне функция, поведениеcontramapне определено fможет вернуть любое значение- Значения возвращенные
fпроверять не надо
- Если
-
contramapдолжен возвращать значение того же Contravariant
Значение, которое реализует спецификация Apply, должно также реализовывать спецификацию Functor.
v.ap(u.ap(a.map(f => g => x => f(g(x)))))эквивалентноv.ap(u).ap(a)(композиция)
ap :: Apply f => f a ~> f (a -> b) -> f bЗначение Apply должно предоставить метод ap. Метод ap принимает один аргумент:
a.ap(b)
-
bдолжен быть функцией- Если
bне является функцией, поведениеapне определено
- Если
-
aдолжен быть любым значением Apply -
apдолжны применять функцию Applybсо значением Applya- Значения возвращенные этой функцией проверять не надо
Значение, которое реализует спецификация Applicative, должно также реализовывать спецификацию Apply.
v.ap(A.of(x => x))эквивалентноv(точный)A.of(x).ap(A.of(f))эквивалентноА.(Ф(Х))(гомоморфизм)A.of(y).ap(u)эквивалентноu.ap(A.of(f => f(y)))(перестановка)
of :: Applicative f => a -> f aЗначение Applicative должно предоставить метод of в её представлении типа. Функция of принимает один аргумент:
F.of(a)
Получив значение f, можно получить представление типа через свойство constructor:
f.constructor.of(a)
-
ofдолжен вернуть значение того же Applicative- Значения возвращенные
aпроверять не надо
- Значения возвращенные
Значение, которое реализует спецификацию Alt, должно также реализовать спецификацию Functor.
a.alt(b).alt(c)эквивалентноa.alt(b.alt(c))(ассоциативность)a.alt(b).map(f)эквивалентноa.map(f).alt(b.map(f))(распределённость)
alt :: Alt f => f a ~> f a -> f aЗначение Alt должно предоставить метод alt. Метод alt принимает один аргумент:
a.alt(b)
-
bдолжен быть значением того же Alt- Если
bне тот же Alt, поведениеaltне определено aиbмогут содержать любое значение того же типа- Значения содержащиеся в
aиbпроверять не надо
- Если
-
altдолжен вернуть значение того же Alt
Значение, которое реализует спецификацию Plus, также должно реализовать спецификацию Alt.
x.alt(A.zero())эквивалентноx(точный справа)A.zero().alt(x)эквивалентноx(точный слева)A.zero().map(f)эквивалентноA.zero()(упразднение)
zero :: Plus f => () -> f aЗначение Plus должно предоставить метод zero в её представлении типа:
A.zero()
Получив значение x, можно получить представление типа через свойство constructor:
x.constructor.zero()
zeroдолжен возвращать значение того же Plus
Значение, которое реализует спецификацию Alternative, должно также реализовать спецификации Applicative и Plus.
x.ap(f.alt(g))эквивалентноx.ap(f).alt(x.ap(g))(распределённость)x.ap(A.zero())эквивалентноA.zero()(упразднение)
u.reduceэквивалентноu.reduce((acc, x) => acc.concat([x]), []).reduce
reduce :: Foldable f => f a ~> ((b, a) -> b, b) -> bЗначение Foldable должно предоставить метод reduce. Метод reduce принимает два аргумента:
u.reduce(f, x)
-
fдолжен быть двоичной функцией- Если
fне функция, поведениеreduceне определено - Первый аргумент
fдолжен быть такого же типа, какx fдолжен возвращать значение такого же типа, какx- Значения возвращенные
fпроверять не надо
- Если
-
x- это первоначальное аккумулятивное значение для преобразованияxпроверять не надо
Значение, которое реализует спецификацию Traversable, должно также реализовывать спецификации Functor и Foldable.
-
t(u.traverse(F, x => x))эквивалентноu.traverse(G, t)для любогоtтакого, чтоt(a).map(f)эквивалентноt(a.map(f))(нормализованность) -
u.traverse(F, F.of)эквивалентноF.of(u)для любой ApplicativeF(точный) -
u.traverse(Compose, x => new Compose(x))эквивалентноnew Compose(u.traverse(F, x => x).map(x => x.traverse(G, x => x)))дляCompose, определенных ниже, и любых ApplicativesFиG(композиция)
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 :: Applicative f, Traversable t => t a ~> (TypeRep f, a -> f b) -> f (t b)Значение Traversable должно предоставить метод traverse. Метод traverse принимает два аргумента:
u.traverse(A, f)
-
Aдолжен быть представлением типа Applicative -
fдолжен быть функцией, которая возвращает значение- Если
fне функция, поведениеtraverseне определено fдолжен возвращать значение типа, представленногоA
- Если
-
traverseдолжен возвращать значение типа представленногоA
Значение, которое реализует спецификацию Chain, должно также реализовывать спецификацию Apply.
m.chain(f).chain(g)эквивалентноm.chain(x => f(x).chain(g))(ассоциативность)
chain :: Chain m => m a ~> (a -> m b) -> m bЗначение Chain должно предоставить метод chain. Метод chain принимает один аргумент:
m.chain(f)
-
fдолжен быть функцией, которая возвращает значение- Если
fне функция, поведениеchainне определено fдолжен возвращать значение того же Chain
- Если
-
chainдолжен возвращать значение того же Chain
Значение, которое реализует спецификацию ChainRec, должно реализовать спецификацию Chain.
M.chainRec((next, done, v) => p(v) ? d(v).map(done) : n(v).map(next), i)эквивалентно(function step(v) { return p(v) ? d(v) : n(v).chain(step); }(i))(эквивалентность)- Использование
M.chainRec(f, i)должно быть максимально подобным самостоятельному вызовуf
chainRec :: ChainRec m => ((a -> c, b -> c, a) -> m c, a) -> m bЗначение ChainRec должно предоставить метод chainRec в его представлении типа. Метод chainRec принимает два аргумента:
M.chainRec(f, i)
Получив значение m, можно получить представление типа через свойство constructor:
m.constructor.chainRec(f, i)
-
fдолжен быть функцией, которая возвращает значение-
Если
fне функция, поведениеchainRecне определено -
fпринимает три аргументаnext,done,valuenext- это функция, которая принимает один аргумент того же типа, какiи может вернуть любое значениеdone- это функция, которая принимает один аргумент и возвращает тот же тип, возвращаемое значениеnextvalue- это значение такого же типа, какi
-
fдолжен возвращать значение того же ChainRec, который содержит значение, возвращаемое изdoneилиnext
-
-
chainRecдолжен возвращать значение того же ChainRec, который содержит то же значение, что и аргументdone
Значение, которое реализует спецификацию Monad, должно также реализовать спецификации Applicative and Chain.
M.of(a).chain(f)эквивалентноf(a)(точный слева)m.chain(M.of)эквивалентноm(точный справа)
Значение, которое реализует спецификацию Extend, должно реализовать спецификацию Functor.
w.extend(g).extend(f)эквивалентноw.extend(_w => f(_w.extend(g)))
extend :: Extend w => w a ~> (w a -> b) -> w bЗначение Extend должно предоставить метод extend. Метод extend принимает один аргумент:
w.extend(f)
-
fдолжен быть функцией, которая возвращает значение- Если
fне функция, поведениеextendнеопределено fдолжен возвращать значение типаv, для некоторой переменнойv, содержащейся вw- Значения возвращенные
fпроверять не надо
- Если
-
extendдолжен возвращать значение того же Extend
Значение, которое реализует спецификацию Comonad, должно реализовать спецификацию Extend.
w.extend(_w => _w.extract())эквивалентноw(точный слева)w.extend(f).extract()эквивалентноf(w)(точный справа)
extract :: Comonad w => w a ~> () -> aЗначение Comonad должно предоставить метод extract. Метод extract не принимает никаких аргументов:
w.extract()
-
extractдолжен возвращать значение типаv, для некоторой переменнойv, содержащиеся вwvдолжен иметь тот же тип, что возвращаетfвextend
Значение, которое реализует спецификацию Bifunctor, должно также реализовать спецификацию Functor.
p.bimap(a => a, b => b)эквивалентнор(точный)p.bimap(a => f(g(a)), b => h(i(b))эквивалентноp.bimap(g, i).bimap(f, h)(композиция)
bimap :: Bifunctor f => f a c ~> (a -> b, c -> d) -> f b dЗначение Bifunctor должно предоставить метод bimap. Метод bimap принимает два аргумента:
c.bimap(f, g)
-
fдолжен быть функцией, которая возвращает значение- Если
fне функция, поведениеbimapне определено fможет вернуть любое значение- Значение, возвращенное
f, проверять не надо
- Если
-
gдолжен быть функцией, которая возвращает значение- Если
gне функция, поведениеpromapне определено gможет возвращать любое значение- Значение, возвращенное
g, проверять не надо
- Если
-
bimapдолжен возвращать значение того же Bifunctor
Значение, которое реализует спецификацию Profunctor, должно также реализовать спецификацию Functor.
p.promap(a => a, b => b)эквивалентнор(точный)p.promap(a => f(g(a)), b => h(i(b)))эквивалентноp.promap(f, i).promap(g, h)(композиция)
promap :: Profunctor p => p b c ~> (a -> b, c -> d) -> p a dЗначение Profunctor должно предоставить метод promap. Метод profunctor принимает два аргумента:
c.promap(f, g)
-
fдолжен быть функцией, которая возвращает значение- Если
fне функция, поведениеpromapне определено fможет вернуть любое значение- Значение, возвращенное
f, проверять не надо
- Если
-
gдолжен быть функцией, которая возвращает значение- Если
gне функция, поведениеpromapне определено gможет возвращать любое значение- Значение, возвращенное
g, проверять не надо
- Если
-
promapдолжен возвращать значение того же Profunctor
При создании типов данных, которые удовлетворяют нескольким алгебрам, авторы могут выбрать для реализации определенных методов другие методы. Реализации:
-
mapможет быть реализован с помощьюapиof:function(f) { return this.ap(this.of(f)); }
-
mapможет быть реализован с помощьюchainиof:function(f) { return this.chain(a => this.of(f(a))); }
-
mapможет быть реализован с помощьюbimap:function(f) { return this.bimap(a => a, f); }
-
mapможет быть реализован с помощьюpromap:function(f) { return this.promap(a => a, f); }
-
apможет быть реализован с помощьюchain:function(m) { return m.chain(f => this.map(f)); }
-
reduceможет быть реализован так: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может быть реализован так: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; }
Если тип данных реализует метод, который может быть реализован, его поведение должно быть эквивалентно реализации (или реализациям).
- Если есть больше, чем один способ реализации методов и законов, реализация должна выбрать один и представить обертку для других применений.
- Не рекомендуется перегружать спецификации методов. Можно легко в результате получить сломанное и неправильное поведение.
- Рекомендуется выбрасывать предупреждение на незадокументированное применение.
- Контейнер
Id, реализующий множество методов, доступен вinternal/id.js.
Существует также Спецификация Static Land

