简体   繁体   English

为什么以及如何继续Monad解决回调地狱? 换句话说:RX或FRP = Continuation Monad? 如果不=,有什么区别?

[英]Why and how the Continuation Monad solves the callback hell? In other words : Is RX or FRP = Continuation Monad ? IF not =, what is the difference?

Here and here it is said that the Continuation Monad solves the callback hell. 这里这里据说Continuation Monad解决了回调地狱。

RX and FRP also solve the Callback hell. RX和FRP也解决了Callback地狱。

If all these three tools solve the Callback hell then the following question arises: 如果所有这三个工具都能解决回调问题,那么就会产生以下问题:

In Erik's video it is said that RX=Continuation Monad. 在Erik的视频中,据说RX = Continuation Monad。 Is that really true? 这是真的吗? If yes, could you show the mapping? 如果是,你能展示映射吗?

IF RX is not = Cont. 如果RX不= =续。 Monad then what is the difference between RX and Continuation Monad? Monad那么RX和Continuation Monad有什么区别?

Similarly, what is the difference between FRP and the Continuation Monad ? 同样,FRP和Continuation Monad有什么区别?

In other words, assuming that the reader knows what FRP or RX is, how can the reader easily understand what the Continuation Monad is ? 换句话说,假设读者知道什么是FRP或RX,读者如何才能轻松理解Continuation Monad是什么?

Is it possible/easy to understand what the Continuation Monad is by comparing it with RX or FRP ? 通过将它与RX或FRP进行比较,是否可能/易于理解Continuation Monad是什么?

I'm not familiar with RX, but regarding FRP and the continuation monad, they're fundamentally different concepts. 我不熟悉RX,但对于FRP和延续monad,它们是根本不同的概念。

Functional reactive programming is a solution to the problem of working with time-dependent values and events. 功能反应式编程是解决与时间相关的值和事件的问题的解决方案。 With events, you can solve the callback problem by sequencing computations in such a way that when one finishes, an event is sent and it triggers the next one. 对于事件,您可以通过对计算进行排序来解决回调问题,这样一旦完成,就会发送一个事件并触发下一个事件。 But with callbacks you really don't care about time, you just want to sequence computations in a specific way, so unless your whole program is based on FRP, it's not the optimal solution. 但是回调你真的不关心时间,你只想以特定的方式对计算进行排序,所以除非你的整个程序都是基于FRP,否则它不是最佳解决方案。

The continuation monad has nothing to do with time at all. 延续monad与时间完全没有关系。 It's an abstract concept of computations that can (loosely speaking) take "snapshots" of the current evaluation sequence and use them to "jump" to those snapshots arbitrarily. 这是一个抽象的计算概念,可以(松散地说)获取当前评估序列的“快照”,并使用它们“任意”“跳转”到这些快照。 This allows to create complex control structures such as loops and coroutines . 这允许创建复杂的控制结构,例如循环和协同程序 Now have a look at the definition of the continuation monad: 现在来看看continuation monad的定义:

newtype Cont r a = Cont { runCont :: (a -> r) -> r}

This is essentially a function with a callback! 这本质上是一个回调函数! It's a function that accepts a continuation (callback) that will continue the computation after a is produced. 这是一个接受的延续(回调)后,将继续计算函数a生产。 So if you have several functions that take continuations, 所以如果你有几个功能可以延续,

f1 :: (Int -> r) -> r

f2 :: Int -> (Char -> c) -> c

f3 :: Char -> (String -> d) -> d

their composition becomes somewhat messy: 他们的构图变得有点凌乱:

comp :: String
comp = f1 (\a -> f2 a (\b -> f3 b id))

Using continuations, it becomes very straightforward, we just need to wrap them in cont and then sequence them using the monadic bind operaiton >>= : 使用continuation,它变得非常简单,我们只需要将它们包装在cont ,然后使用monadic bind operaiton >>=对它们进行排序:

import Control.Monad.Cont

comp' :: String
comp' = runCont (cont f1 >>= cont . f2 >>= cont . f3) id

So continuations are a direct solution to the callback problem. 因此,continuation是回调问题的直接解决方案。

If you look at how the continuation monad is defined in Haskell, it looks like this: 如果你看看在Haskell中如何定义continuation monad,它看起来像这样:

data Cont r a =  Cont { runCont :: (a -> r) -> r }

By itself, this is completely pure, and doesn't represent real world effects or time. 就其本身而言,这是完全纯粹的,并不代表现实世界的影响或时间。 Even in its current form it can be used to represent temporal/IO effects, simply by choosing r to be a type involving IO . 即使在当前形式下,它可用于表示时间/ IO效果,只需选择r作为涉及IO的类型即可。 For our purposes however, we're going to do something slightly different. 然而,出于我们的目的,我们将做一些稍微不同的事情。 We're going to substitute the concrete -> with a type parameter: 我们将使用类型参数替换具体的->

data Cont p r a = Cont { runCont :: p (p a r) r }

What is the use of this? 有什么用? In Haskell, we only have pure functions which map some input domain to an output domain. 在Haskell中,我们只有将一些输入域映射到输出域的纯函数。 In other languages, we can have such functions, but we can additionally define impure "functions", which (in addition to producing some arbitrary output for a given input), may implicitly perform side effects. 在其它语言中,我们可以有这样的功能,但是我们可以定义不纯“功能”,它(除了生产对于给定输入一些任意的输出),可以隐式地执行的副作用。

Here is an example of both in JS: 以下是JS中的两个示例:

// :: Int -> Int -> Int
const add = x => y => x + y

// :: String -!-> ()
const log = msg => { console.log(msg); }

Note that log is not a pure function that produces a value representing an effect, which is how such things are encoded in pure languages like Haskell. 请注意, log不是一个产生表示效果的值的纯函数,这就是如Haskell这样的纯语言编码的方式。 Instead, the effect is associated with the mere invocation of log . 相反,效果与仅仅调用log To capture this, we can use different arrows when we talk about pure functions and impure "functions" ( -> and -!-> , respectively). 为了捕获这个,我们可以在谈论纯函数和不纯的“函数”(分别是->-!-> )时使用不同的箭头。

So, returning to your question about how the continuation monad solves callback hell, it turns out that (in JavaScript at least), most of the APIs that give rise to callback hell can be massaged quite easily into values of the form Cont (-!->) () a , which I'll henceforth refer to as Cont! a 所以,回到关于延续monad如何解决回调地狱的问题,事实证明(至少在JavaScript中),产生回调地狱的大多数API都可以很容易地按照形式Cont (-!->) () a值进行按摩Cont (-!->) () a ,我将在此称之为Cont! a Cont! a . Cont! a

Once you have a monadic API, the rest is easy; 一旦你有一个monadic API,其余的很容易; you can traverse structures full of continuations into continuations of a structure, use do notation to write multi-step computations akin to async/await , use monad transformers to equip continuations with additional behavior (for example error handling), etc. 你可以将充满连续的结构遍历到结构的延续中,使用do notation编写类似于async/await多步计算,使用monad变换器为continuation配备额外的行为(例如错误处理)等。

The monad instance looks much the same as it does in Haskell: monad实例与Haskell中的实例看起来非常相似:

// :: type Cont p r a = p (p a r) r
// :: type Cont! = Cont (-!->) ()

// :: type Monad m = { pure: x -> m x, bind: (a -> m b) -> m a -> m b }

// :: Monad Cont!
const Cont = (() => {
  // :: x -> Cont! x
  const pure = x => cb => cb(x)
  // :: (a -> Cont! b) -> Cont! a -> Cont! b
  const bind = amb => ma => cb => ma(a => amb(a)(cb))

  return { pure, bind }
})()

Here are a couple of examples of modelling the setTimeout and readFile APIs available in Node JS as impure continuations: 以下是将Node JS中可用的setTimeoutreadFile API建模为不纯的延续的几个示例:

// :: FilePath -> Cont! (Either ReadFileError Buffer)
const readFile = path => cb => fs.readFile(path, (e, b) => cb(e ? Left(e) : Right(b)))

// :: Int -> v -> Cont! v
const setTimeout = delay => v => cb => setTimeout(() => cb(v), delay)

As a contrived example, here's the "callback hell" we enter when we use the standard APIs to read a file, wait five seconds, then read another file: 作为一个人为的例子,这里是我们使用标准API读取文件时输入的“回调地狱”,等待五秒钟,然后读取另一个文件:

fs.readFile("foo.txt", (e, b1) => {
  if (e) { throw e }
  setTimeout(() => {
    fs.readFile("bar.txt", (e, b2) => {
      if (e) { throw e }
      console.log(b1.toString("utf8") + b2.toString("utf8"))
    })
  }, 5000)
})

And here is the equivalent program using the continuation monad: 这是使用continuation monad的等效程序:

const ECont = EitherT(Cont)

const decode = buf => buf.toString("utf8")

const foo = readFile("foo.txt") |> ECont.map(decode)
const bar = readFile("bar.txt") |> ECont.map(decode)

// Imaginary do notation for JS for purposes of clarity
const result = do(ECont)([
  [s1, foo],
  ECont.lift(delay_(5000)),
  [s2, bar],
  ECont.pure(s1 + s2)
])

const panic = e => { throw e }
const log = v => { console.log(v) }

// No side effects actually happen until the next line is invoked
result(Either.match({ Left: panic, Right: log }))

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM