Here和here据说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是什么?
答案 0 :(得分:4)
我不熟悉RX,但对于FRP和延续monad,他们从根本上是不同的概念。
Functional reactive programming是解决与时间相关的值和事件的问题的解决方案。对于事件,您可以通过对计算进行排序来解决回调问题,这样一旦完成,就会发送一个事件并触发下一个事件。但是回调你真的不关心时间,你只想以特定的方式对计算进行排序,所以除非你的整个程序都是基于FRP,否则它不是最佳解决方案。
continuation monad与时间毫无关系。它是一个抽象的计算概念,可以(松散地说)采取"快照"当前评估序列并将其用于" jump"那些快照任意。这允许创建复杂的控制结构,例如循环和coroutines。现在看一下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是回调问题的直接解决方案。
答案 1 :(得分:0)
如果您看一下Haskell中延续monad的定义,它看起来像这样:
data Cont r a = Cont { runCont :: (a -> r) -> r }
就其本身而言,这是完全纯净的,并不代表现实世界的效果或时间。即使以其当前形式,也可以通过将r
选择为涉及IO
的类型来使用 来表示时间/ 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
。为此,在讨论纯函数和不纯的“函数”(分别为->
和-!->
时),可以使用不同的箭头。
所以,回到你关于延续单子解决了如何回调地狱的问题,事实证明,(在JavaScript中至少),大多数产生回调地狱的API可以很容易地按摩到形式的值{ {1}},以下将其称为Cont (-!->) () a
。
一旦有了monadic API,其余的就很容易了;你可以遍历结构充分延续的成结构的延续,使用做记号来写多步骤计算类似于Cont! a
,使用单子变压器装备延续用额外的行为(例如错误处理)等。>
在单子实例看起来就像它同样在Haskell:
async/await
下面是几个模拟的实施例中的// :: 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 }
})()
和setTimeout
在节点JS可用的API为不纯延续:
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)
和这里是使用了延续单子等效方案:
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)
})