[英]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中可用的setTimeout
和readFile
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.