Это спецификация для общих алгебраических типов в JavaScript на основе спецификации Fantasy Land.
Тип в Static Land это словарь (JavaScript объект) со статическими функциями в качестве значений. 'Статический' означает что функции не используют this
, они могут быть убраны у объекта типа. Объект типа просто контейнер для функций.
const {of, map} = MyType
// Должно работать
map(x => x + 1, of(41)) // MyType(42)
Функции из объекта типа часто называются "методами" типа. Но запомните что они не методы в JS представлении (они не используют this
).
Каждый метод в этой спецификации имеет описание типа, оно выглядит так.
map :: Functor f => Type f ~> (a → b, f a) → f b
Мы используем синтаксис похожий на Хаскель. Вы можете узнать о нём из вики Ramda'ы или из книги "В основном адекватное руководство профессора Фрисби по функциональному программированию".
Эта спецификация использует расширения для синтаксиса описания типов:
(a, b) → c
обозначает булеву функцию, которая не каррирована. Тоже самое для большего числа аргументов.Type a
обозначает типизированный словарь типаa
. Например, функция с описанием(Type f, f a) → f a
может быть вызвана такfn(F, F.of(1))
.~>
обозначает доступ к свойствам JavaScript объекта. Например,fn :: Type f ~> (f a) → f a
может быть применена такF.fn(F.of(1))
.
Если метод вызывается с неправильными типами, поведение неопределено. Также, если метод принимает функцию, он должен применять функцию только в соответствии с описанием типа, т.е. обеспечивать правильное количество аргументов и правильные типы.
Все реализации методов должны использовать только информацию о типе аргумента, изветсную из описания типа метода. не разрешено проверять аргументы или значения, которые они возвращают или содержат для получения большей информации о их типах. Другими словами методы должны быть параматрически полиморфны.
Например, давайте рассмотрим описание метода функтора map
:
map :: Functor f => Type f ~> (a → b, f a) → f b
В нём есть три типа переменных: f
, a
, and b
. Также у нас есть некоторые ограничения:
Functor f
говорит чтоf a
— значение Functor.Type f ~>
означает, что мы пишем реализациюmap
для типизированного словаряType f
. В этом случае мы знаем, что определенный типType f
работает с методом, поэтому мы знаем все оf
.
У нас нет никаких ограничений для типов a
и b
, так что мы ничего не знаем о них. И нам нельзя проверять их.
Вот реализация Maybe, которая нарушает требование параметризации, хотя вписывается в описание типа:
Maybe.Nothing = {type: 'Nothing'}
Maybe.of = x => {
if (x === undefined) { // проверка неразрешена
return Maybe.Nothing
}
return {type: 'Just', value: x}
}
Maybe.map = (f, v) => {
// это законная проверка `f a`, потому что мызнаем структуру `f`
if (v.type === 'Nothing') {
return v
}
const a = v.value
const b = f(a)
if (b === undefined) { // проверка неразрешена
return Maybe.Nothing
}
return Maybe.of(b)
}
Эквивалентности для данного значения должна соответствоавать определению, что два значения могут быть безопасно поменяны местами в программе, что подтверждает абстракцию.
Например:
- Два списка эквивалентны, если они эквивалентны по всем показателям.
- Два обычных JavaScript объекта, представленные как словари, эквивалентны, когда они эквивалентны по всем ключам.
- Два промиса эквивалентны, когда они возвращают эквивалентные значения.
- Две функции эквивалентны, если они дают эквивалентные результаты для эквивалентных входных данных.
мы используем символ ≡
в правилах для обозначения эквивалентности.
Алгебра представляет собой набор значений(экземпляров типа и других значений), набор операторов(методов типа), которые зависимы и должны подчиняться некоторым правилам.
Каждая алгебра — это отдельная спецификация. Алгебра может иметь зависимости от реализаций других алгебр.
Алгебра также может содержать дргие методы алгебр, которые могут быть получены из новых методов. Если тип имеет метод, который может быть получен, его поведение должно быть эквивалентно тому, из которого он получен.
- Setoid
- Semigroup
- Monoid
- Functor
- Bifunctor
- Contravariant
- Profunctor
- Apply
- Applicative
- Alt
- Plus
- Alternative
- Chain
- ChainRec
- Monad
- Foldable
- Extend
- Comonad
- Traversable
equals :: Setoid s => Type s ~> (s, s) → Boolean
- Рефлексивность:
S.equals(a, a) === true
- Симметрия:
S.equals(a, b) === S.equals(b, a)
- Транзитивность: если
S.equals(a, b)
иS.equals(b, c)
, тогдаS.equals(a, c)
concat :: Semigroup s => Type s ~> (s, s) → s
- Ассоциативность:
S.concat(S.concat(a, b), c) ≡ S.concat(a, S.concat(b, c))
- Semigroup
empty :: Monoid m => Type m ~> () → m
- Точный справа:
M.concat(a, M.empty()) ≡ a
- Точный слева:
M.concat(M.empty(), a) ≡ a
map :: Functor f => Type f ~> (a → b, f a) → f b
- Точный:
F.map(x => x, a) ≡ a
- Композиция:
F.map(x => f(g(x)), a) ≡ F.map(f, F.map(g, a))
- Functor
bimap :: Bifunctor f => Type f ~> (a → b, c → d, f a c) → f b d
- Точный:
B.bimap(x => x, x => x, a) ≡ a
- Композиция:
B.bimap(x => f(g(x)), x => h(i(x)), a) ≡ B.bimap(f, h, B.bimap(g, i, a))
- Functor's map:
A.map = (f, u) => A.bimap(x => x, f, u)
contramap :: Contravariant f => Type f ~> (a → b, f b) → f a
- Точный:
F.contramap(x => x, a) ≡ a
- Композиция:
F.contramap(x => f(g(x)), a) ≡ F.contramap(g, F.contramap(f, a))
- Functor
promap :: Profunctor f => Type f ~> (a → b, c → d, f b c) → f a d
- Точный:
P.promap(x => x, x => x, a) ≡ a
- Композиция:
P.promap(x => f(g(x)), x => h(i(x)), a) ≡ P.promap(g, h, P.promap(f, i, a))
- Functor's map:
A.map = (f, u) => A.promap(x => x, f, u)
- Functor
ap :: Apply f => Type f ~> (f (a → b), f a) → f b
- Композиция:
A.ap(A.ap(A.map(f => g => x => f(g(x)), a), u), v) ≡ A.ap(a, A.ap(u, v))
- Apply
of :: Applicative f => Type f ~> a → f a
- Точный:
A.ap(A.of(x => x), v) ≡ v
- Гомоморфизм:
A.ap(A.of(f), A.of(x)) ≡ A.of(f(x))
- Перестановка:
A.ap(u, A.of(y)) ≡ A.ap(A.of(f => f(y)), u)
- Functor's map:
A.map = (f, u) => A.ap(A.of(f), u)
- Functor
alt :: Alt f => Type f ~> (f a, f a) → f a
- Ассоциативность:
A.alt(A.alt(a, b), c) ≡ A.alt(a, A.alt(b, c))
- Распределённость:
A.map(f, A.alt(a, b)) ≡ A.alt(A.map(f, a), A.map(f, b))
- Alt
zero :: Plus f => Type f ~> () → f a
- Точный справа:
P.alt(a, P.zero()) ≡ a
- Точный слева:
P.alt(P.zero(), a) ≡ a
- Упразднение:
P.map(f, P.zero()) ≡ P.zero()
- Applicative
- Plus
- Распределённость:
A.ap(A.alt(a, b), c) ≡ A.alt(A.ap(a, c), A.ap(b, c))
- Упразднение:
A.ap(A.zero(), a) ≡ A.zero()
- Apply
chain :: Chain m => Type m ~> (a → m b, m a) → m b
- Ассоциативность:
M.chain(g, M.chain(f, u)) ≡ M.chain(x => M.chain(g, f(x)), u)
- Apply's ap:
A.ap = (uf, ux) => A.chain(f => A.map(f, ux), uf)
- Chain
chainRec :: ChainRec m => Type m ~> ((a → c, b → c, a) → m c, a) → m b
- Эквивалентность:
C.chainRec((next, done, v) => p(v) ? C.map(done, d(v)) : C.map(next, n(v)), i) ≡ (function step(v) { return p(v) ? d(v) : C.chain(step, n(v)) }(i))
- Использование
C.chainRec(f, i)
должно быть максимально подобным самостоятельному вызовуf
.
- Applicative
- Chain
- Точный слева:
M.chain(f, M.of(a)) ≡ f(a)
- Точный справа:
M.chain(M.of, u) ≡ u
- Functor's map:
A.map = (f, u) => A.chain(x => A.of(f(x)), u)
reduce :: Foldable f => Type f ~> ((a, b) → a, a, f b) → a
F.reduce ≡ (f, x, u) => F.reduce((acc, y) => acc.concat([y]), [], u).reduce(f, x)
extend :: Extend e => Type e ~> (e a → b, e a) → e b
- Associativity:
E.extend(f, E.extend(g, w)) ≡ E.extend(_w => f(E.extend(g, _w)), w)
- Functor
- Extend
extract :: Comonad c => Type c ~> c a → a
C.extend(C.extract, w) ≡ w
C.extract(C.extend(f, w)) ≡ f(w)
C.extend(f, w) ≡ C.map(f, C.extend(x => x, w))
- Functor
- Foldable
traverse :: (Traversable t, Applicative f) => Type t ~> (Type f, (a → f b), t a) → f (t b)
- Нормализованность:
f(T.traverse(A, x => x, u)) ≡ T.traverse(B, f, u)
для любогоf
такого чтоB.map(g, f(a)) ≡ f(A.map(g, a))
- Точный:
T.traverse(F, F.of, u) ≡ F.of(u)
для любого ApplicativeF
- Композиция:
T.traverse(ComposeAB, x => x, u) ≡ A.map(v => T.traverse(B, x => x, v), T.traverse(A, x => x, u))
дляComposeAB
определённого ниже и для любого ApplicativesA
иB
const ComposeAB = {
of(x) {
return A.of(B.of(x))
},
ap(a1, a2) {
return A.ap(A.map(b1 => b2 => B.ap(b1, b2), a1), a2)
},
map(f, a) {
return A.map(b => B.map(f, b), a)
},
}
reduce
метод Foldable:
F.reduce = (f, acc, u) => {
const of = () => acc
const map = (_, x) => x
const ap = f
return F.traverse({of, map, ap}, x => x, u)
}
map
метод Functor:
F.map = (f, u) => {
const of = (x) => x
const map = (f, a) => f(a)
const ap = (f, a) => f(a)
return F.traverse({of, map, ap}, f, u)
}