我一直在学习函数式编程,并遇到过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.我错过了什么?
答案 0 :(得分:22)
UDATE。 看到这个新图书馆 证明函子和monad运算符 用于基于回调的普通函数 那些没有问题的问题 如下所述:
https://github.com/dmitriz/cpsfy
它不是一个算符,因为 composition preservation law (将函数组合发送到其图像的合成) 被违反:
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)
包含在承诺中。相反,当.then
中的函数返回“thenable”时,Promise Spec需要不同的行为。根据{{3}},id = a => a
项下存储的标识函数then
称为
id(resolvePromise, rejectPromise)
其中resolvePromise
和rejectPromise
是promise解析过程提供的两个回调函数。但是,为了得到解决或拒绝,必须调用其中一个回调函数,这些函数永远不会发生!因此,由此产生的承诺仍处于待决状态。
在这个例子中,
promise.then(x => g(f(x)))
用身份函数a => a
来实现,
而
promise.then(f).then(g)
永远处于待定状态。
因此,这两个承诺并不相同
因此违反了仿函法。
因为即使是Applicative规范中的自然变换定律,也就是Pointed Functor(同态定律)的一部分,也被违反了:
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上的这个例子:Applicative
在这个例子中,一个承诺得以实现,而另一个承诺未决,因此这两个在任何意义上都不相同,违反了法律。
更新。
Promise 一个Functor / Applicative / Monad之间似乎存在混淆,以及通过改变方法或添加新方法使其成为的方法。但是,Functor必须具有已经提供的map
方法(不一定以此名称),并且作为Functor显然取决于此方法的选择。只要法律得到满足,方法的实际名称就不起任何作用。
对于Promises,.then
是最自然的选择,如下所述,它不符合Functor法则。就我所见,其他Promise方法都不会以任何可想到的方式使它成为Functor。
是否可以定义符合法律的其他方法是另一回事。我所知道的这个方向的唯一实现是由https://codepen.io/dmitriz/pen/wjqyjY?editors=0011提供的。
但是需要支付相当大的代价:不仅需要定义全新的map
方法,而且需要更改promise对象本身:{{1} promise可以保存“theneable”作为值,而原生JS Promise则不能。这种变化是实质性的,并且必须避免违反实施例中的法律,如下所述。特别是,我没有任何方法在没有这些根本性变化的情况下将Promise变成Functor(或Monad)。
答案 1 :(得分:4)
Promise
(a lot like)是monad,因为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
是第一个参数)
Promise API提供then
的另一个签名,then(Promise<A>, Func<A, Promise<B>>) : Promise<B>
。这个版本显然符合monadic bind(>>=
)的签名。自己尝试一下,它有效。
然而,为monad拟合签名并不意味着Promise 是monad。它还需要满足monad的代数定律。
monad必须满足的法则是结社法(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是一个monad,我们可以派生ap
并从中获取一个应用程序,给我们一些非常好的语法,带有一些不明智的hackery:
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)
答案 2 :(得分:1)
Promise将包含then属性的对象视为特例,该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
之所以发生这种情况,是因为resolve将then属性下的函数视为回调,将then链的继续作为参数传递,而不是创建一个包含它的promise。这样,它就不能像单元一样工作,并且会违反单子法则。
但是,在不包含then属性的值上,它应该用作monad。
答案 3 :(得分:-1)
一个承诺可能是Monad,实际上可能不是标准的本机实现,因为Promise对象上没有 Bind方法的明确定义。 大多数人似乎误解了这一概念,将 then 方法等同于 monadic绑定。 如果我们将Promise扩展为:
._inp {
color: red;
padding-left: 10px;
}
我们可以验证3条单子律:
<form>
<input type="text" class="_inp" value="10 x 30" />
</form>
要进行更广泛的讨论,请查看本文Rediscovering Promises in Javascript
在这里玩小提琴-Promise as Monad : Monadic Laws
答案 4 :(得分:-3)
据我所知,Promises是Functors,Applicative Functors和Monads,因为它们遵守functor和monads法则。
好的,让我们研究一下仿函数案例。要使Promise成为Functor的一个实例,我们必须为Promises定义fmap
函数(a -> b) - f a -> f b
,fmap
将通过Functor法则。什么是算子法?
fmap id = id
fmap (p . q) = (fmap p) . (fmap q)
id
是身份功能。我们可以简单地在JS中实现它,如var id = x => x
.
中的(p . q)
就像数学中的组合操作一样。它本质上是JS中的var dot = p => q => x => p(q(x))
。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));
&#13;
所以是的Promise是Functors,如果你查看Monad laws,你很容易就会知道它们也是Monads。