就 Monad 而言,Promises 的递归连接意味着什么?

What are the implications of the recursive joining of Promises in terms of Monads?

我知道 Javascript 的承诺在技术上既不是函子也不是 Haskell 意义上的单子,因为(除其他外)

始终提供具有正确类型的函数可以轻松绕过第一个问题 a -> Promise b

第二个问题明显违反了参数化多态函数的参数化特性,即不能构造m (m a)结构。但是这个结构在 promises/asynchronous 计算的上下文中意味着什么?我想不出 Promise (Promise a) 的有意义的语义,其中 Promise 是一个单子。那么我们失去了什么?递归连接的含义是什么?

只要我们非常务实(这就是我们在编程时应该做到的 Javascript),我们不能声称 Promise 是 Javascript 中的 monad如果我们处理边缘情况?

The first issue can easily be bypassed by always providing a function with the right type a -> Promise a.

或者不使用 then 作为 monad 的 bind 操作,而是使用一些类型正确的操作。 Creed is a functionally minded promise library that provides map and chain methods which implements the Fantasy-land spec 对于代数类型。

第二个问题也可以通过相同的方法绕过,方法是不使用 resolve 而是使用 fulfill,并将静态 of 方法作为单元函数。

But what would this structure mean in the context of promises/asynchronous computations?

这是对价值的承诺。并非每个可构造类型都需要“有意义”或“有用”:-)

然而,Fetch API 提供了类似类型的一个很好的例子:它 returns 一个解析为 Response 对象的承诺,它再次“包含”一个承诺解析为响应的正文。

所以 Promise (Promise a) 可能只有一个成功结果值,也可以通过 Promise a 访问它,但是两个级别的承诺

  • 可能会在不同的时间完成,添加一个“中间步骤”
  • 可能会因不同原因而拒绝 - 例如外层代表网络问题,内层代表解析问题

请注意,Promise 类型应该有第二个类型变量用于拒绝原因,类似于 Either。两级 Promise err1 (Promise err2 a)Promise err a.

完全不同

I know that Javascript's promises are technically neither functors nor monads in the sense of Haskell

然而,您还没有提到最大的问题:它们是可变的。如果我们考虑执行顺序,从 pending 到 settled 状态的转换是一个副作用,它会破坏引用透明性,当然我们通常的 promise 用例涉及很多 IO,而这些 IO 根本不是由 promise 类型建模的。

Promise.delay(50).then(() => Promise.delay(50))
// does something different than
const a = Promise.delay(50); a.then(() => a)

应用 monad 法则很有趣,偶尔也很有用,但我们确实需要大量的实用主义。

I know that Javascript's promises are technically neither functors nor monads in the sense of Haskell

Not only in the sense of Haskell,以任何其他方式也是如此。

  • they include a bind operation that falls back to map when a pure function is passed in (and thus has an ambiguous type)

没有 bind JS 原生 promises 提供的运算符

  • both the Promise constructor and resolve (aka return) join nested promises recursively

我猜你的意思是展开 "theneables",即调用存储在 then prop 下的函数,只要有这样的函数。

The first issue can easily be bypassed by always providing a function with the right type a -> Promise b.

这不会像 map 例如当 map(f) 用于 f = x => {then: a => a}.

The second issue obviously violates the parametricity trait of parametric polymorphic functions, i.e. one cannot construct a m (m a) structure.

确实如此。

But what would this structure mean in the context of promises/asynchronous computations? I cannot think of a meaningful semantics for Promise (Promise a), where Promise is a monad. So what do we lose? What are the implications of the recursive joining?

您需要允许存储任意值。 Promise 不允许存储 neables(不展开),这就是问题所在。所以你需要改变对象和方法的语义。允许对象在不改变的情况下存储 theneables 并实现 .bind aka .chain 精确地展开(或连接)theneables 一次 - 无递归。

这就是 creed does for promise-like objects and cpsfy for callback-based(又名延续传递样式)的功能。


Provided we are pretty pragmatic (and that's what we should be when we're programming Javascript), can't we claim that a Promise is a monad in Javascript if we take care of the edge cases?

编写安全、简洁和可组合的代码是务实的。冒着通过泄漏抽象引入细微错误的风险,这些错误可能会导致关键软件崩溃,并产生深远的后果。 每个边缘案例都是此类风险的潜在来源。

在这方面,声称 Promise 是一个单子除了不正确之外弊大于利。它确实有害,因为您不能安全地将单子转换应用于承诺。例如。使用任何符合 monadic 接口的代码都是不安全的,就像它们是 monad 一样。如果使用得当,monad 可以帮助抽象和重用您的代码,而不是引入检查和寻找边缘情况的行。