Почему обещания Монады?

Я изучал функциональное программирование и столкнулся с Monads, Functors и Applicatives.

По моему мнению, применяются следующие определения:

a) (A => B) => C [A] => C [B] | Функтор

b) (A => C [B]) => C [A] => C [B] | монада

c) (C [A => B]) => C [A] => C [B] | Прикладное

(ссылка: https://thedet.wordpress.com/2012/04/28/functors-monads-applicatives-can-be-so-simple/)

Кроме того, я понимаю, что Монада является частным случаем Functor. Как и в случае, она применяет функцию, которая возвращает завернутое значение в завернутое значение и возвращает завернутое значение.

Когда мы используем Promise.then(func), мы передаем Promise (т.е. C [A]) функцию, которая обычно имеет сигнатуру A => B и возвращает другое Promise (т.е. C [B]). Поэтому я думал, что Promise будет только Functor, а не Monad, поскольку func возвращает B, а не C [B].

Однако, по поиску в Google, я узнал, что Promise - это не только Functor, но и Monad. Интересно, почему, поскольку func не возвращает завернутое значение C [B], а просто B. Что мне не хватает?

Ответ 1

UDATE. Посмотрите эту новую библиотеку, доказывающую операторы функтора и монады для простых основанных на обратном вызове функций, у которых нет проблем с neableable, как описано ниже:

https://github.com/dmitriz/cpsfy


Обещание JS не является ни Функтором, ни Аппликативным, ни Монадой

Он не является функтором, поскольку нарушается закон сохранения композиции (отправка композиций функций в композиции их изображений):

promise.then(x => g(f(x))) 

НЕ эквивалентно

promise.then(f).then(g)

Что это означает с практической точки зрения, это никогда не безопасно для рефакторинга

promise
  .then(x => f(x))
  .then(y => g(y))

в

promise
  .then(x => g(f(x))

как это было бы, были бы Promise функтором.

Доказательство функтора о нарушении закона. Вот контрпример:

//Functor composition preservation law:
// promise.then(f).then(g)  vs  promise.then(x => g(f(x)))

// f takes function 'x' 
// and saves it in object under 'then' prop:
const f = x => ({then: x})

// g returns the 'then' prop from object 
const g = obj => obj.then

// h = compose(g, f) is the identity
const h = x => g(f(x))

// fulfill promise with the identity function
const promise = Promise.resolve(a => a)

// this promise is fulfilled with the identity function
promise.then(h)
       .then(res => {
           console.log("then(h) returns: ", res)
       })
// => "then(h) returns: " a => a

// but this promise is never fulfilled
promise.then(f)
       .then(g)
       .then(res => {
           console.log("then(f).then(g) returns: ", res)
       })
// => ???

// because this one isn't:
promise.then(f)
       .then(res => {
           console.log("then(f) returns: ", 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 требуется другое поведение, когда функция внутри .then возвращает "thenable". Согласно 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)

Доказательство. Вот контрпример:

// identity function saved under 'then' prop
const v = ({then: a => a})

// 'g' returns 'then' prop from object 
const g = obj => obj.then

// 'g(v)' is the identity function
Promise.resolve(g(v)).then(res => {
    console.log("resolve(g(v)) returns: ", res)
})
// => "resolve(g(v)) returns: " a => a

// 'v' is unwrapped into promise that remains pending forever
// as it never calls any of the callbacks
Promise.resolve(v).then(g).then(res => {
    console.log("resolve(v).then(g) returns: ", res)
})
// => ???

Этот пример на Codepen: https://codepen.io/dmitriz/pen/wjqyjY?editors=0011

Заключение

В этом примере снова одно обещание выполнено, а другое - в ожидании, поэтому оба не эквивалентны ни в каком смысле, нарушая закон.


ОБНОВИТЬ.

Что означает "быть функтором"?

Кажется, существует путаница между тем, что Promise является Functor/Applicative/Monad как есть, и способами сделать это так, изменив свои методы или добавив новые. Тем не менее, у Functor должен быть уже предоставлен метод map (не обязательно под этим именем), и его наличие явно зависит от выбора этого метода. Фактическое название метода не играет никакой роли, пока соблюдены законы.

Для Обещаний .then является наиболее естественным выбором, который не соответствует закону Функтора, как описано ниже. Насколько я понимаю, ни один из других методов Promise не сделал бы его Функтором.

Изменение или добавление методов

Другое дело, могут ли быть определены другие методы, соответствующие законам. Единственная реализация в этом направлении, о которой я знаю, обеспечивается библиотекой creed.

Но за это приходится платить немалую цену: нужно определить не только совершенно новый метод map, но и сами объекты обещания: обещание creed может содержать в качестве значения "theneable", в то время как собственное обещание JS Promise может ". т. Это изменение является существенным и необходимым, чтобы не нарушать закон в примерах, описанных ниже. В частности, я не знаю ни одного способа превратить Обещание в Функтор (или Монаду) без таких фундаментальных изменений.

Ответ 2

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 обещаний поставляет еще одну подпись для then, then(Promise<A>, Func<A, Promise<B>>): Promise<B>. Эта версия, очевидно, соответствует сигнатуре для монадического связывания (>>=). Попробуйте сами, это работает.

однако подгонка подписи для монады не означает, что Обещание является монадой. он также должен удовлетворять алгебраическим законам для монад.

законы, которым должна удовлетворять монада, - закон ассоциативности

(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
);

Я думаю, что кто-нибудь, кто знаком с обещаниями, может видеть, что все это должно быть правдой, но не стесняйтесь попробовать это сами.

потому что Promise - это монада, мы можем получить 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)

Ответ 3

Обещания - это не монады над объектами, содержащими свойство

Обещания обрабатывают объекты, содержащие свойство 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

пример по codepen

Это происходит потому, что решение обрабатывает функцию под свойством then как обратный вызов, передавая продолжение цепочки в качестве аргумента, а не создавая обещание, содержащее его. Таким образом, он не функционирует как единица и не нарушает законы монады.

Однако над значениями, которые не содержат свойства then, он должен функционировать как монада.

Ответ 4

Обещание может быть связано с 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

и поиграй с скрипкой здесь - Обещай как Монада: Монадические Законы

Ответ 5

По мне, обещания - это функторы, аппликативные функторы и монады, поскольку они подчиняются законам-функторам и монадам.

Хорошо, давайте изучим случай функтора. Для обещаний быть примером Functor мы должны определить функцию fmap (a → b) - fa → fb для Promises, а fmap - законы Functor. Что такое законы функтора?

fmap id      = id
fmap (p . q) = (fmap p) . (fmap q)
  • id - функция тождества. Мы можем просто реализовать его в JS, например var id = x => x
  • . in (p. q) - операция композиции, как и в Math. Это, по существу, 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));