它的工作方式与 Scheme 的 call/cc 非常相似。您需要考虑到它在 Cont monad 中。
实际函数是使用 ContT 定义的。ContT 是一个 monad 转换器,它允许将延续添加到其他 monad 中,但让我们先看看它是如何与 Identity monad 一起工作的,并将我们自己限制在 Cont 中。
Cont r a = Cont {runCont :: (a->r)->r}
在这里,Cont r a
表示一个可以计算某个类型值的a
函数,因为给定一个类型的函数,a->r
它可以计算一个类型的值r
。
这显然是一个单子:
return x = Cont $ \f -> f x
(一个简单的“计算”类型的值a
)
ma >>= h = Cont $ \f -> runCont ma $ \a -> runCont (h a) f
(这里ma :: Cont r a
,和h :: a -> Cont r b
)
(类型值的计算a
ma,可以变成类型值的计算b
- runCont ma
is given h
,给定类型的值,它a
“知道”如何产生类型值的计算b
- 可以提供具有f :: b -> r
计算类型值的函数r
)
本质上,h
是 的延续,ma
并且>>=
bindsma
和它的延续产生函数组合的延续(函数“隐藏”在里面ma
产生a
,函数“隐藏”在里面h
产生b
)。这是您正在寻找的“堆栈”。
让我们从简化类型开始(不使用ContT
):
callCC :: ((a -> Cont r b) -> Cont r a) -> Cont r a
在这里, callCC 使用一个函数来构造一个给定延续的延续。
您似乎也缺少重要的一点。callCC
只有在之后有延续才有意义callCC
- 即有延续要通过。让我们考虑它是一个do
-block 的最后一行,这与说它必须与它绑定的东西相同>>=
:
callCC f >>= return "blah"
会做。这里重要的一点是,callCC
当你看到这个上下文时,可以更容易理解 的操作,当你看到它在 的左侧时>>=
。
知道如何>>=
工作,并考虑到 的右关联性>>=
,您可以看到h
incallCC f = cont $ \h -> runCont (f (\a -> cont $ \_ -> h a)) h
实际上是使用当前延续构建的 - 它是使用h
出现在右侧的-从 callCC 之后的行到末尾>>=
的整个- 块构建的:do
(callCC f) >>= h =
Cont $ \g -> runCont
(cont $ \h -> runCont (f (\a -> cont $ \_ -> h a)) h) $
\a -> runCont (h a) g =
[reduction step: runCont (Cont x) => x]
Cont $ \g -> (\h -> runCont (f (\a -> Cont $ \_ -> h a)) h) $
\a -> runCont (h a) g =
[(\h -> f) (\a -> ...) => f [h/(\a -> ...)] -- replace
occurrences of h with (\a -> ...)]
Cont $ \g -> runCont (f (\a -> Cont $ \_ -> (\b -> runCont (h b) g) a)) $
\a -> runCont (h a) g =
[(\b -> runCont (h b) g) a => runCont (h a) g]
Cont $ \g -> runCont (f (\a -> Cont $ \_ -> runCont (h a) g)) $
\a -> runCont (h a) g
您可以在这里看到\_ -> runCont (h a) g
本质上将如何忽略调用传递给的函数之后的延续- 并“切换堆栈”,切换到被调用位置f
的当前延续。h
callCC
(如果是链中的最后一个,则可以应用类似的推理callCC
,尽管在这种情况下“当前延续”是传递给的函数不太清楚runCont
)
最后一点是,如果实际调用发生在由 计算的延续内部,那么它runCont (f...) h
并没有真正使用这个 last ,如果这种情况发生的话。h
h
f