为什么 Promise 是 Monad? [英] Why are Promises Monads?

查看:21
本文介绍了为什么 Promise 是 Monad?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在学习函数式编程,并遇到过 Monad、Functor 和 Applicative.

根据我的理解,以下定义适用:

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/)

此外,我知道 Monad 是 Functor 的一个特例.如在,它应用一个函数返回一个包装值到一个包装值并返回一个包装值.

当我们使用 Promise.then(func) 时,我们向 Promise(即 C[A])传递一个通常具有签名 A => 的函数.B 并返回另一个 Promise(即 C[B]).所以我的想法是 Promise 只是一个 Functor 而不是 Monad,因为 func 返回 B 而不是 C[B].

然而,通过谷歌搜索我发现 Promise 不仅是一个 Functor,而且还是一个 Monad.我想知道为什么,因为 func 不返回包装值 C[B] 而只是 B.我错过了什么?

解决方案

UDATE.看到这个新图书馆证明函子和单子运算符对于简单的基于回调的函数没有 theneables 的问题如下所述:

https://github.com/dmitriz/cpsfy

<小时>

JS Promise 既不是 Functor 也不是 Applicative 也不是 Monad

它不是一个函子,因为成分保存法(将功能的组合发送到其图像的组合)被侵犯了:

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

不等同于

promise.then(f).then(g)

这实际上意味着什么,重构永远不安全

承诺.then(x => f(x)).then(y => g(y))

承诺.then(x => g(f(x))

本来是 Promise 一个函子.

违反函子定律的证明.这是一个反例:

<前>//函子组合保存法则://promise.then(f).then(g) vs promise.then(x => g(f(x)))//f 接受函数`x`//并将其保存在 `then` 属性下的对象中:const f = x => ({然后: x})//g 从对象返回 `then` 属性const g = obj => obj.then//h = compose(g, f) 是身份const h = x => g(f(x))//使用身份函数履行承诺const promise = Promise.resolve(a => a)//这个promise 是通过identity 函数实现的承诺.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 =>一个.

另一方面,f 返回所谓的thenable:

<块引用>

1.2.thenable"是定义 then 方法的对象或函数.

为了支持函子定律,.then 必须简单地将结果 f(x) 包装到 promise 中.相反,当 .then 中的函数返回thenable"时,承诺规范 需要不同的行为".根据 2.3.3.3,身份函数 id = a =>存储在 then 键下的 称为

id(resolvePromise, rejectPromise)

其中resolvePromiserejectPromise 是promise 解析过程提供的两个回调函数.但是,为了被解析或拒绝,必须调用这些回调函数之一,这永远不会发生!因此,产生的承诺仍处于挂起状态.

结论

在这个例子中,promise.then(x => g(f(x)))由恒等函数 a => 满足.一个,然而promise.then(f).then(g)永远处于挂起状态.因此这两个承诺不等价因此违反了函子定律.

<小时>

Promise 既不是 Monad 也不是适用

因为即使是来自 Pointed Functor 规范,这是适用的一部分(同态定律),违反了:

Promise.resolve(g(x)) 不等同于 Promise.resolve(x).then(g)

证明.这是一个反例:

<前>//标识函数保存在 `then` 属性下const v = ({then: a => a})//`g` 从对象返回 `then` 属性const g = obj => obj.then//`g(v)` 是恒等函数Promise.resolve(g(v)).then(res => {console.log("resolve(g(v)) 返回:", res)})//=> "resolve(g(v)) 返回:" a => a//`v` 被解包成永远挂起的 promise//因为它从不调用任何回调Promise.resolve(v).then(g).then(res => {console.log("resolve(v).then(g) 返回:", res)})//=> ???

Codepen 上的这个例子:https://codepen.io/dmitriz/pen/wjqyjY?editors=0011

结论

在这个例子中,一个承诺已经实现,而另一个未决,因此两者在任何意义上都不是等价的,违反了法律.

<小时>

更新.

成为函子"究竟是什么意思?

Promise 成为一个 Functor/Applicative/Monad 与通过改变其方法或添加新方法使其成为这样的方法之间似乎存在混淆.然而,一个 Functor 必须已经提供了一个 map 方法(不一定在这个名字下),作为一个 Functor 显然取决于这个方法的选择.方法的实际名称没有任何作用,只要满足规律即可.

对于 Promises,.then 是最自然的选择,它不符合 Functor 定律,如下所述.据我所知,其他 Promise 方法都不会以任何可以想象的方式使它成为 Functor.

更改或添加方法

是否可以定义符合法律的其他方法是另一回事.我所知道的这个方向的唯一实现是由 creed library 提供的.

但是有一个相当大的代价:不仅需要定义全新的map方法,而且promise对象本身也需要改变:一个creed promise 可以持有一个theneable"作为值,而原生的 JS Promise 则不能.如下所述,为了避免违反示例中的法律,这一更改是实质性的和必要的.特别是,我不知道有什么方法可以在没有这些根本性改变的情况下将 Promise 变成 Functor(或 Monad).

I've been learning about functional programming and have come across Monads, Functors and Applicatives.

From my understanding the following definitions apply:

a) ( A=>B ) => C[A] => C[B] | Functor

b) ( A=>C[B] ) => C[A] => C[B] | Monad

c) ( C[A=>B] ) => C[A] => C[B] | Applicative

(reference: https://thedet.wordpress.com/2012/04/28/functors-monads-applicatives-can-be-so-simple/)

Furthermore, I understand a Monad is a special case of a Functor. As in, it applies a function that returns a wrapped value to a wrapped value and returns a wrapped value.

When we use Promise.then(func), we are passing the Promise(i.e. C[A]) a function which normally has signature A => B and return another Promise (i.e. C[B]). So my thinking was that a Promise would only be a Functor and not a Monad as func returns B and not C[B].

However, googling I found out that a Promise is not only a Functor, but also a Monad. I wonder why, as func does not return a wrapped value C[B] but just B. What am I missing?

解决方案

UDATE. See this new library proving functor and monad operators for plain callback-based functions that do not have the issues with theneables as outlined below:

https://github.com/dmitriz/cpsfy


The JS Promise is neither a Functor nor an Applicative nor a Monad

It is not a functor, because the composition preservation law (sending compositions of functions to compositions of their images) is violated:

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

is NOT equivalent to

promise.then(f).then(g)

What this means in practical terms, it is never safe to refactor

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

to

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

as it would have been, were Promise a functor.

Proof of the functor law violation. Here is a counter-example:

//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)
       })

Here is this example on Codepen: https://codepen.io/dmitriz/pen/QrMawp?editors=0011

Explanation

Since the composition h is the identity function, promise.then(h) simply adopts the state of promise, which is already fulfilled with the identity a => a.

On the other hand, f returns the so-called thenable:

1.2. "thenable" is an object or function that defines a then method.

To uphold the functor law, .then would have to simply wrap into promise the result f(x). Instead, the Promise Spec requires a different behavior when the function inside .then returns a "thenable". As per 2.3.3.3, the identity function id = a => a stored under then key is called as

id(resolvePromise, rejectPromise)

where resolvePromise and rejectPromise are two callback functions provided by the promise resolution procedure. But then, in order to be resolved or rejected, one of these callback functions must be called, which never happens! So the resulting promise remains in the pending state.

Conclusion

In this example, promise.then(x => g(f(x))) is fulfilled with the identity function a => a, whereas promise.then(f).then(g) remains in the pending state forever. Hence these two promises are not equivalent and therefore the functor law is violated.


Promise is neither a Monad nor an Applicative

Because even the natural transform law from the Pointed Functor Spec, that is part of being Applicative (the homomorphism law), is violated:

Promise.resolve(g(x)) is NOT equivalent to Promise.resolve(x).then(g)

Proof. Here is a counter-example:

// 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)
})
// => ???

This example on Codepen: https://codepen.io/dmitriz/pen/wjqyjY?editors=0011

Conclusion

In this example again one promise is fulfilled, whereas the other is pending, therefore the two are not equivalent in any sense, violating the law.


UPDATE.

What does exactly "being a Functor" mean?

There seems to be a confusion between Promise being a Functor/Applicative/Monad as it is, and ways to make it such by changing its methods or adding new ones. However, a Functor must have a map method (not necessarily under this name) already provided, and being a Functor clearly depends on the choice of this method. The actual name of the method does not play any role, as long as the laws are satisfied.

For the Promises, .then is the most natural choice, which fails the Functor law as explained below. None of the other Promise methods would make it a Functor either in any conceivable way, as far as I can see.

Changing or adding methods

It is a different matter whether other methods can be defined that conform to the laws. The only implementation in this direction that I am aware of is provided by the creed library.

But there is a considerable price to pay: not only entirely new map method needs to be defined, but also the promise objects themselves need to be changed: a creed promise can hold a "theneable" as value, while the native JS Promise can't. This change is substantial and necessary to avoid breaking the law in the examples as one explained below. In particular, I am not aware of any way to make the Promise into a Functor (or a Monad) without such fundamental changes.

这篇关于为什么 Promise 是 Monad?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆