为什么Promises Monads?

时间:2017-08-16 11:16:57

标签: javascript functional-programming monads es6-promise functor

我一直在学习函数式编程,并遇到过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.我错过了什么?

5 个答案:

答案 0 :(得分:22)

UDATE。 看到这个新图书馆 证明函子和monad运算符 用于基于回调的普通函数 那些没有问题的问题 如下所述:

https://github.com/dmitriz/cpsfy

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

它不是一个算符,因为 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)

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

结论

在这个例子中, promise.then(x => g(f(x))) 用身份函数a => a来实现, 而 promise.then(f).then(g) 永远处于待定状态。 因此,这两个承诺并不相同 因此违反了仿函法。

承诺既不是2.3.3.3也不是Monad

因为即使是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

结论

在这个例子中,一个承诺得以实现,而另一个承诺未决,因此这两个在任何意义上都不相同,违反了法律。

更新。

究竟“作为一个Functor”是什么意思?

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)

承诺不是包含then属性的对象的单子对象

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

example on codepen

之所以发生这种情况,是因为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 bfmap将通过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不同,每次部分应用函数时,您将获得执行相同操作的不同函数。因此,以下法律中的公平检查将会失败,但如果您检查结果值,它们将通过。

&#13;
&#13;
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;
&#13;
&#13;

所以是的Promise是Functors,如果你查看Monad laws,你很容易就会知道它们也是Monads。