为什么 Promises 是 Monad?

新手上路,请多包涵

我一直在学习函数式编程,并且遇到过 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/

此外,我知道 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。我错过了什么?

原文由 Jack Spar 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 772
1 个回答

更新。请参阅这个新库,它为没有 theneables 问题的普通基于回调的函数提供仿函数和 monad 运算符:

https://github.com/dmitriz/cpsfy


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

它不是函子,因为违反了 组合保存法则(将函数的组合发送到它们的图像的组合):

 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 一个函子。

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

//函子组合保存法则:
// promise.then(f).then(g) 对比 promise.then(x => g(f(x)))

// f 接受函数 `x`
// 并将其保存在 `then` 属性下的对象中:
const f = x => ({then: x})

// g 从对象返回 `then` 属性
const g = obj => obj.then

// h = compose(g, f) 是恒等式
常量 h = x => g(f(x))

// 使用身份函数履行承诺
常量承诺 = Promise.resolve(a => a)

// 这个承诺是通过身份函数实现的
承诺.then(h)
       .then(res => {
           console.log("then(h) 返回:", res)
       })
// => "then(h) 返回:" a => a

// 但是这个承诺永远不会实现
承诺.then(f)
       .然后(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

解释

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 .

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

1.2. “thenable”是定义 then 方法的对象或函数。

为了维护仿函数法则, .then 必须简单地将结果包装成 promise f(x) 。相反,当内部函数 .then 返回“thenable”时, Promise 规范 需要不同的行为。根据 2.3.3.3 ,身份函数 id = a => a 存储在 then 密钥下被称为

id(resolvePromise, rejectPromise)

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

结论

在此示例中, promise.then(x => g(f(x))) 由身份函数 a => a 实现,而 promise.then(f).then(g) 永远处于挂起状态。因此这两个承诺不等价,因此违反了函子法则。


Promise 既不是 Monad 也不是 Applicative

因为即使是来自 Pointed Functor Spec 的自然变换法则,也就是 Applicative 的一部分(同态法则),也被违反了:

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

证明。 这是一个反例:

// identity 函数保存在 `then` 属性下
const v = ({then: a => a})

// `g` 从对象返回 `then` prop
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 已实现,而另一个 promise 尚未完成,因此两者在任何意义上都不等价,这违反了法律。


更新。

“成为函子”到底是什么意思?

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

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

更改或添加方法

至于能不能定义出 其他符合规律的方法,那就另当别论了。据我所知,在这个方向上的唯一实现是由 creed 库 提供的。

但是要 付出相当大的代价:不仅需要定义全新的 map 方法,还需要更改 promise 对象本身:a creed promise can hold a “ theneable”作为值,而原生 JS Promise 不能。这种变化是实质性的,并且是必要的,以避免在下面解释的示例中违反法律。特别是,我不知道有什么方法可以在不进行此类基本更改的情况下将 Promise 变成 Functor(或 Monad)。

原文由 Dmitri Zaitsev 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题