繁体   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?

这里这里据说Continuation Monad解决了回调地狱。

RX和FRP也解决了Callback地狱。

如果所有这三个工具都能解决回调问题,那么就会产生以下问题:

在Erik的视频中,据说RX = Continuation Monad。 这是真的吗? 如果是,你能展示映射吗?

如果RX不= =续。 Monad那么RX和Continuation Monad有什么区别?

同样,FRP和Continuation Monad有什么区别?

换句话说,假设读者知道什么是FRP或RX,读者如何才能轻松理解Continuation Monad是什么?

通过将它与RX或FRP进行比较,是否可能/易于理解Continuation Monad是什么?

我不熟悉RX,但对于FRP和延续monad,它们是根本不同的概念。

功能反应式编程是解决与时间相关的值和事件的问题的解决方案。 对于事件,您可以通过对计算进行排序来解决回调问题,这样一旦完成,就会发送一个事件并触发下一个事件。 但是回调你真的不关心时间,你只想以特定的方式对计算进行排序,所以除非你的整个程序都是基于FRP,否则它不是最佳解决方案。

延续monad与时间完全没有关系。 这是一个抽象的计算概念,可以(松散地说)获取当前评估序列的“快照”,并使用它们“任意”“跳转”到这些快照。 这允许创建复杂的控制结构,例如循环和协同程序 现在来看看continuation monad的定义:

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

这本质上是一个回调函数! 这是一个接受的延续(回调)后,将继续计算函数a生产。 所以如果你有几个功能可以延续,

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

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

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

他们的构图变得有点凌乱:

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

使用continuation,它变得非常简单,我们只需要将它们包装在cont ,然后使用monadic bind operaiton >>=对它们进行排序:

import Control.Monad.Cont

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

因此,continuation是回调问题的直接解决方案。

如果你看看在Haskell中如何定义continuation monad,它看起来像这样:

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

就其本身而言,这是完全纯粹的,并不代表现实世界的影响或时间。 即使在当前形式下,它可用于表示时间/ IO效果,只需选择r作为涉及IO的类型即可。 然而,出于我们的目的,我们将做一些稍微不同的事情。 我们将使用类型参数替换具体的->

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

有什么用? 在Haskell中,我们只有将一些输入域映射到输出域的纯函数。 在其它语言中,我们可以有这样的功能,但是我们可以定义不纯“功能”,它(除了生产对于给定输入一些任意的输出),可以隐式地执行的副作用。

以下是JS中的两个示例:

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

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

请注意, log不是一个产生表示效果的值的纯函数,这就是如Haskell这样的纯语言编码的方式。 相反,效果与仅仅调用log 为了捕获这个,我们可以在谈论纯函数和不纯的“函数”(分别是->-!-> )时使用不同的箭头。

所以,回到关于延续monad如何解决回调地狱的问题,事实证明(至少在JavaScript中),产生回调地狱的大多数API都可以很容易地按照形式Cont (-!->) () a值进行按摩Cont (-!->) () a ,我将在此称之为Cont! a Cont! a

一旦你有一个monadic API,其余的很容易; 你可以将充满连续的结构遍历到结构的延续中,使用do notation编写类似于async/await多步计算,使用monad变换器为continuation配备额外的行为(例如错误处理)等。

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 }
})()

以下是将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)

作为一个人为的例子,这里是我们使用标准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)
})

这是使用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