Javascript 如何应用callcc,以便它提供一个转义延续机制,用于延续monad

Javascript 如何应用callcc,以便它提供一个转义延续机制,用于延续monad,javascript,functional-programming,monads,continuations,callcc,Javascript,Functional Programming,Monads,Continuations,Callcc,我尝试在Javascript中实现continuation monad来处理continuation传递样式和异步控制流。这是我的继续学习单子: //辅助函数 const log=prefix=>x=>console.log(prefix,x); 常量addk=x=>y=>k=>setTimeout((x,y)=>k(x+y),0,x,y); 常量inck=x=>k=>setTimeout(x=>k(x+1),0,x); 常数sqr=x=>x*x; //连续单子 常数cont={ of:x=>

我尝试在Javascript中实现continuation monad来处理continuation传递样式和异步控制流。这是我的继续学习单子:

//辅助函数
const log=prefix=>x=>console.log(prefix,x);
常量addk=x=>y=>k=>setTimeout((x,y)=>k(x+y),0,x,y);
常量inck=x=>k=>setTimeout(x=>k(x+1),0,x);
常数sqr=x=>x*x;
//连续单子
常数cont={
of:x=>k=>k(x),
map:ftor=>f=>k=>ftor(x=>k(f(x)),
ap:ftor=>gtor=>k=>ftor(x=>gtor(f=>k(f(x)),
链:ftor=>mf=>k=>ftor(x=>mf(x)(k)),
callcc:f=>k=>f(x=>()=>k(x))(k)
};
//将普通一元函数映射为异步函数:
续地图(增量(9))(sqr)(日志(“地图”));
//使用异步二进制CPS函数链接两个异步CPS计算
常数comp1=连续映射(增量(4))(sqr);
常数comp2=连续映射(增量(9))(sqr);
连续链(comp1)(x=>cont.chain(comp2)(y=>addk(x)(y)))(log(“chain”))延续
延续是一种强大的抽象。让我强调一下。延续是一种非常强大的抽象。为什么延续如此强大?这是因为延拓只是一个函数[1],以后会更进一步

那么,如果延拓只是一个函数,那么为什么它如此特殊呢? 是的,延续只是一个函数。然而,让延续如此特别的是它所代表的东西。延续表示“计算的其余部分”(也称为计算上下文)。例如,考虑下面的方案表达式:

(add1(*3 x))
;       |_____|
;          |
;     计算
(增补1[]
; |_______|
;     |
;  上下文
这里的计算
(*3 x)
具有上下文
(add1[])
,其中
[]
表示一个孔。可以根据计算结果堵塞该孔。对于某些
结果
,这写为
(add1[result])
。延续只是上下文的表示。例如,函数
(lambda(result)(add1 result))
表示上下文
(add1[])

另一方面,计算
(*3 x)
也可以表示为函数。它被表示为函数
(lambda(context)(context(*3 x))
,其中
context
是表示计算上下文的延续。应该注意的是,Haskell中的类型表示计算本身,而不是其上下文

好吧,但是什么让延续如此强大? 正如我之前所说的,continuation只是一个函数,特别是一个函数不仅可以被调用一次,还可以被任意调用多次,甚至永远不会被调用。这就是为什么延续如此强大

例如,考虑上述计算<代码>(* 3 x)< /代码>,表示为函数:

(lambda(上下文)
(上下文(*3 x)))
如果我们多次应用
上下文
会怎么样?我们可以使用它将结果加倍,如下所示:

(lambda(上下文)
(+
(上下文(*3 x))
(上下文(*3 x)))
如果给定的
上下文
add1
,则这将生成答案
(*2(add1(*3 x))

另一方面,如果我们从未应用过
上下文
?我们可以简化评估:

(lambda(上下文)
(*3 x))
这正是
call/cc
所做的。它忽略上下文并简单地返回一个答案,从而缩短了评估过程。例如,考虑:

(呼叫/抄送(lambda(短路)
(增补1(短路(*3 x‘‘‘)'))
这里,计算
(*3 x)
具有上下文
add1
。但是,我们通过将
call/cc
(即
short-circuit
)的上下文应用于计算结果来缩短计算。因此,我们忽略了原始上下文(即
add1
),并返回了一个答案

call/cc
是如何实现的? 现在我们了解了延续,让我们看看Haskell中的定义:

callCC::((a->contr b)->Cont r a)->Cont r a
-- |___________________________|
--               |
--f
callCC f=Cont$\k->runCont(f(\a->Cont$\\uk->a))k
--        __|___            |______________________|
--       |      |                       |
--(a->r)短路
应该注意,
k
是当前的延续(即整个表达式的延续)。函数
f
callCC
的唯一输入。它返回一个
Cont r a
,表示要执行的整个计算。我们将其应用于
k
,以得到计算结果

但是,在计算内部,我们可以随时调用
short-circuit
,以使计算短路。此函数获取一个结果并返回一个忽略其上下文的计算,并将当前的延续
k
应用于结果,从而缩短计算

将所有这些都放在JavaScript中。 我们了解计划中的延续是什么。我们了解了
callCC
在Haskell中的工作原理。让我们利用新发现的知识在JavaScript中实现continuations和
callCC

var Cont=defclass({
构造函数:函数(runCont){
this.runCont=runCont;
},
地图:功能(f){
返回新的Cont(k=>this.runCont(x=>k(f(x)));
},
应用:功能(g){
返回新的Cont(k=>this.runCont(f=>g.runCont(x=>k(f(x '))));
},
绑定:函数(f){
返回新的Cont(k=>this.runCont(x=>f(x).runCont(k));