日期。请参阅这个新的库,证明 functor 和 monad 操作符是基于回调的简单函数,这些函数没有下面概述的 theneables 问题:
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) 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))
// 使用身份函数履行Promise
const promise = Promise.resolve(a => a)
// 这个promise 是通过identity 函数实现的
Promise.then(h)
.then(res => {
console.log("then(h) 返回:", res)
})
// => "then(h) 返回:" a => a
// 但是这个Promise永远不会兑现
Promise.then(f)
.then(g)
.then(res => {
console.log("then(f).then(g) 返回:", res)
})
// => ???
// 因为这不是:
Promise.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
必须简单地将结果包装到 promise 中f(x)
。相反,当内部函数返回“thenable”时,Promise Spec需要不同的行为.then
。根据2.3.3.3,id = a => a
存储在then
key下的身份函数被称为
id(resolvePromise, rejectPromise)
其中resolvePromise
和rejectPromise
是Promise解析过程提供的两个回调函数。但是,为了被解析或拒绝,必须调用这些回调函数之一,这永远不会发生!因此,产生的Promise仍处于挂起状态。
结论
在这个例子中,
promise.then(x => g(f(x)))
由身份函数 完成a => a
,而
promise.then(f).then(g)
永远保持挂起状态。因此这两个Promise是不等价的,因此违反了函子定律。
因为即使是来自Pointed Functor Spec的自然变换定律,也就是Applicative 的一部分(同态定律),也被违反了:
Promise.resolve(g(x)) is NOT equivalent to 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已经实现,而另一个未决,因此两者在任何意义上都不是等价的,违反了法律。
更新。
“作为一个函子”到底是什么意思?
Promise作为Functor/Applicative/Monad 与通过改变其方法或添加新方法来实现它的方法之间似乎存在混淆。然而,一个 Functor 必须已经提供了一个map
方法(不一定在这个名字下),作为一个 Functor 显然取决于这个方法的选择。方法的实际名称没有任何作用,只要满足规律即可。
对于 Promises 来说,.then
是最自然的选择,它不符合 Functor 定律,如下所述。就我所见,其他 Promise 方法都不会以任何可以想象的方式使其成为 Functor。
更改或添加方法
是否可以定义其他符合规律的方法是另一回事。我所知道的这个方向的唯一实现是由creed library提供的。
但是需要付出相当大的代价:不仅map
需要定义全新的方法,还需要更改Promise对象本身:creed
Promise可以将“theneable”作为值,而原生 JS Promise 则不能。如下所述,为了避免违反示例中的法律,这一更改是实质性的和必要的。特别是,我不知道有什么方法可以使 Promise 变成一个 Functor(或一个 Monad)而没有这些根本性的改变。