Почему Монады Обещаний?
Я изучал функциональное программирование и встречал монады, функторы и аппликативы.
Насколько я понимаю, применяются следующие определения:
а) ( A=>B) => C[A] => C[B] | Функтор
б) ( A=>C[B]) => C[A] => C[B] | монада
в) ( C[A=>B]) => C[A] => C[B] | Прикладное
(ссылка: https://thedet.wordpress.com/2012/04/28/functors-monads-applicatives-can-be-so-simple/)
Кроме того, я понимаю, что Монада - это особый случай Функтора. Например, он применяет функцию, которая возвращает упакованное значение для упакованного значения и возвращает упакованное значение.
Когда мы используем Promise.then(func)
, мы передаем Promise (т.е. C [A]) функцию, которая обычно имеет подпись A => B
и верните другое обещание (например, C [B]). Так что я думал, что Обещание будет только Функтором, а не Монадой, как func
возвращает B, а не C [B].
Однако, погуглив, я обнаружил, что Обещание - это не только Функтор, но и Монада. Интересно почему, как func
не возвращает упакованное значение C [B], а просто B. Чего мне не хватает?
4 ответа
Обещание не является ни Функтором, ни Аппликативным, ни Монадой
Он не является функтором, поскольку нарушается закон сохранения композиции (отправка композиций функций композициям их изображений):
promise.then(x => g(f(x))) is NOT equivalent to promise.then(f).then(g)
Что это означает с практической точки зрения, это никогда не безопасно для рефакторинга
promise
.then(x => f(x))
.then(y => g(y))
в
promise
.then(x => g(f(x))
как было бы, были Promise
функтор
Доказательство функтора о нарушении закона. Вот контрпример:
// Закон сохранения композиции функтора: // обещание.then (f).then (g) против обещания.then(x => g(f(x))) // f берет функцию `x` и сохраняет ее в объекте под`then` prop: const f = x => ({затем: x}) // g возвращает подпункт `then` объекта const g = obj => obj.then // h = compose(g, f) это тождество const h = x => g(f(x)) // выполнить обещание с помощью функции идентификации постоянное обещание = Promise.resolve(a => a) // это обещание выполняется с помощью функции идентификации обещание.then(h).then(res => { console.log("then(h) возвращает: ", res) }) // => "then(h) возвращает: " a => a // но это обещание никогда не выполняется Обещание.then(f).then(g).then(res => { console.log("then(f).then(g) возвращает: ", res) }) // =>??? // потому что это не обещание.then(f).then(res => { console.log("then(f) возвращает:", res) })
Вот этот пример на Codepen: https://codepen.io/dmitriz/pen/QrMawp?editors=0011
объяснение
Так как состав h
это тождественная функция, promise.then(h)
просто принимает состояние promise
, который уже выполнен с личностью a => a
,
С другой стороны, f
возвращает так называемый thenable:
1.2. Thenable - это объект или функция, которая определяет метод then.
Чтобы поддержать закон функтора, .then
пришлось бы просто завернуть в обещание результата f(x)
, Вместо этого Promise Spec требует другого поведения, когда функция внутри .then
возвращает "затем". Согласно 2.3.3.3, тождественная функция id = a => a
хранится под then
ключ называется как
id(resolvePromise, rejectPromise)
где resolvePromise
а также rejectPromise
две функции обратного вызова, предоставляемые процедурой разрешения обещаний. Но затем, для разрешения или отклонения, должна быть вызвана одна из этих функций обратного вызова, что никогда не происходит! Таким образом, полученное обещание остается в состоянии ожидания.
Заключение
В этом примереpromise.then(x => g(f(x)))
выполняется с помощью функции тождества a => a
, в то время какpromise.then(f).then(g)
остается в состоянии ожидания навсегда. Следовательно, эти два обещания не эквивалентны, и поэтому закон функтора нарушается.
Обещание не является ни монадой, ни аппликативным
Поскольку даже закон естественного преобразования из Спецификации Указанного Функтора, который является частью Аппликативности (закон гомоморфизма), нарушается:
Promise.resolve(g(x)) is NOT equivalent to Promise.resolve(x).then(g)
Доказательство. Вот контрпример:
// функция идентичности сохранена в подпункте then const v = ({тогда: a => a}) // `g` возвращает объект` then` от объекта const g = obj => obj.then // `g(v)` - тождественная функция Promise.resolve(g(v)). Then(res => { console.log("resol (g (v)) возвращает: ", res) }) // => "resolv (g (v))" возвращает: " a => a // `v` развернуто в обещании, которое остается в ожидании навсегда // так как он никогда не вызывает ни одного из обратных вызовов Promise.resolve(v).then(g).then(res => { console.log("resol (v).then (g) возвращает:", res) }) // =>???
Этот пример на Codepen: https://codepen.io/dmitriz/pen/wjqyjY?editors=0011
Заключение
В этом примере снова одно обещание выполнено, а другое - в ожидании, поэтому оба не эквивалентны ни в каком смысле, нарушая закон.
Promise
является (очень похоже) монадой, потому что then
перегружен
Когда мы используем Promise.then(func), мы передаем Promise(т.е. C[A]) функцию, которая обычно имеет сигнатуру A => B и возвращает другое Promise(то есть C[B]). Поэтому я думал, что Promise будет только Functor, а не Monad, так как func возвращает B, а не C[B].
это верно для then(Promise<A>, Func<A, B>) : Promise<B>
(если вы извините мой псевдокод для типов javascript, я буду описывать функции как this
были первые аргументы)
API Promise предоставляет другую подпись для then
хоть, then(Promise<A>, Func<A, Promise<B>>) : Promise<B>
, Эта версия явно соответствует подписи для монадного связывания (>>=
). Попробуйте сами, это работает.
однако подгонка подписи для монады не означает, что Promise - это монада. это также должно удовлетворить алгебраические законы для монад.
законы, которым должна соответствовать монада, являются законом ассоциативности
(m >>= f) >>= g ≡ m >>= ( \x -> (f x >>= g) )
и законы левой и правой идентичности
(return v) >>= f ≡ f v
m >>= return ≡ m
в JavaScript:
function assertEquivalent(px, py) {
Promise.all([px, py]).then(([x, y]) => console.log(x === y));
}
var _return = x => Promise.resolve(x)
Promise.prototype.bind = Promise.prototype.then
var p = _return("foo")
var f = x => _return("bar")
var g = y => _return("baz")
assertEquivalent(
p.bind(f).bind(g),
p.bind(x => f(x).bind(g))
);
assertEquivalent(
_return("foo").bind(f),
f("foo")
);
assertEquivalent(
p.bind(x => _return(x)),
p
);
Я думаю, что любой, кто знаком с обещаниями, может увидеть, что все это должно быть правдой, но не стесняйтесь попробовать это сами.
потому что обещание является монадой, мы можем вывести ap
и извлеките из этого аппликатив, предоставив нам очень хороший синтаксис с немного опрометчивым хакером:
Promise.prototype.ap = function (px) {
return this.then(f => px.then(x => f(x)));
}
Promise.prototype.fmap = function(f) {
return this.then(x => f(x));
}
// to make things pretty and idiomatic
Function.prototype.doFmap = function(mx) {
return mx.fmap(this);
}
var h = x => y => x + y
// (h <$> return "hello" <*> return "world") >>= printLn
h.doFmap(_return("hello, ")).ap(_return("world!")).bind(console.log)
Обещания - это не монады над объектами, содержащими свойство тогда
Обещания обрабатывают объекты, содержащие свойство then, которое является функцией, как особый случай. Из-за этого они нарушают закон левой идентичности, как показано ниже:
//Law of left identity is violated
// g(v) vs Promise.resolve(v).then(g)
// identity function saved under `then` prop
const v = ({then: x=>x({then: 1})})
// `g` returns the `then` prop from object wrapped in a promise
const g = (obj => Promise.resolve(obj.then))
g(v).then(res =>
console.log("g(v) returns", res))
// "g(v) returns" x => x({ then: 1 })
Promise.resolve(v).then(g)
.then(res =>
console.log("Promise.resolve(v).then(g) returns", res))
// "Promise.resolve(v).then(g) returns" 1
Это происходит потому, что функция разрешения обрабатывает функцию в свойстве then как обратный вызов, передавая продолжение цепи then в качестве аргумента, а не создавая обещание, содержащее его. Таким образом, он не функционирует как единица и вызывает нарушение законов монады.
Однако для значений, которые не содержат свойство then, оно должно функционировать как монада.
Обещание может быть связано с Monad, может быть, не со стандартной нативной реализацией, потому что нет явного определения метода Bind для объекта Promise. Большинство людей, похоже, неправильно поняли концепцию и приравнивают тогдашний метод к монадическому связыванию. если мы расширим Promise с помощью:
Promise.prototype.bind = function(func) {
var initialPromise = this;
return new Promise(function(resolve) {
initialPromise.then(result => func(result).then(x => resolve(x)))
});
}
мы можем проверить 3 монадических закона:
var id = x => new Promise((resolve) => resolve(x));
// Law 1 -Left identity: return a >>= f ≡ f a
var value = 1
var f = x => id(x * 2);
//id(value).bind(f) ==f(value)
id(value).bind(f).then(console.log)
f(value) .then(console.log)
// Law 2 -Right Identity : m >>= return ≡ m
var m = id(1)
//m.bind(id) == m
m.bind(id).then(console.log)
m .then(console.log)
//Law 3 -Associativity: (m flatMap f) flatMap g assert_=== m flatMap { x => f(x) flatMap {g} }
var m = id(1);
var f = x => id(x * 2);
var g = x => id(x * 5);
m.bind(f).bind(g) .then(console.log);
m.bind(x=>f(x).bind(g)) .then(console.log);
для более обширного обсуждения проверьте эту статью Переоткрывающие Обещания в Javascript
и поиграй с скрипкой здесь - Обещай как Монада: Монадические Законы
По моему мнению, Обещания - это функторы, аппликативные функторы и монады, поскольку они подчиняются законам функторов и монад.
Хорошо, давайте изучим случай функтора. Чтобы Promises был экземпляром Functor, мы должны определить fmap
функция (a -> b) - f a -> f b
для обещаний и fmap
должен пройти законы Функтора. Что такое функторные законы?
fmap id = id
fmap (p . q) = (fmap p) . (fmap q)
id
это тождественная функция. Мы можем просто реализовать это в JS, как varid = x => x
-
.
в(p . q)
это операция композиции, как в математике. Это по сутиvar dot = p => q => x => p(q(x))
в JS.
Проблема в JS состоит в том, что объекты, включая функции, являются ссылочными типами, что означает, что в отличие от Haskell, каждый раз, когда вы частично применяете функцию, вы получаете другую функцию, выполняющую то же самое. Так что только проверки справедливости в следующих законах не пройдут, но они пройдут, если вы проверите итоговые значения.
var id = x => x,
dot = f => g => x => f(g(x)),
fmap = f => p => p.then(v => f(v)),
pr1 = Promise.resolve(1);
fmap(id)(pr1) === id(pr1); // false since objects are mutable
fmap(id)(pr1).then(v => console.log(v));
id(pr1).then(v=> console.log(v));
fmap(dot(x => x*2)(y => y+5))(pr1).then(v => console.log(v));
dot(fmap(x => x*2))(fmap(y => y+5))(pr1).then(v => console.log(v));
Так что да, Обещания - это Функторы, и если вы проверите законы Монады, вы можете легко сказать, что они также являются Монадами.