理解Haskell-callCC示例

理解Haskell-callCC示例,haskell,continuations,callcc,Haskell,Continuations,Callcc,我很难理解前一个问题的答案。我希望下面的解释能澄清问题。下面的示例来自 我想我理解这个例子是如何工作的,但是为什么有必要在callCC中使用let表达式来“返回”continuation,以便以后可以使用它呢。因此,我尝试通过以下更简单的示例直接返回continuation并对其进行修改 import Control.Monad.Trans.Class import Control.Monad.Trans.Cont main = flip runContT return $ do li

我很难理解前一个问题的答案。我希望下面的解释能澄清问题。下面的示例来自

我想我理解这个例子是如何工作的,但是为什么有必要在
callCC
中使用
let
表达式来“返回”continuation,以便以后可以使用它呢。因此,我尝试通过以下更简单的示例直接返回continuation并对其进行修改

import Control.Monad.Trans.Class
import Control.Monad.Trans.Cont

main = flip runContT return $ do
    lift $ putStrLn "alpha"
    callCC $ \k -> do
      k ()
      lift $ putStrLn "uh oh..."
    lift $ putStrLn "beta"
    lift $ putStrLn "gamma"
这张照片

alpha
beta
gamma
我将其修改为以下内容

import Control.Monad.Trans.Class
import Control.Monad.Trans.Cont

main = flip runContT return $ do
    lift $ putStrLn "alpha"
    f <- callCC $ \k -> do
      lift $ putStrLn "uh oh..."
      return k
    lift $ putStrLn "beta"
    lift $ putStrLn "gamma"
但是这个例子没有编译,为什么不能这样做呢

<强>编辑< /强>:考虑方案中的无用示例。据我所知,这个计划不会有问题,对吗?但为什么

为什么需要在callCC中有一个let表达式来“return” 继续,以便以后可以使用

这就是continuation的确切用法,即捕获当前执行上下文,然后稍后使用此捕获continuation跳回该执行上下文


您似乎被函数名
callCC
弄糊涂了,它可能向您表明它正在调用一个continuation,但实际上它正在创建一个continuation。

以相反的顺序查看示例

withCC (\k -> let f x = k (f, x)
              in  return (f, 0)) next
next (let f x = next (f, x) in return (f, 0))
最后一个示例由于无限类型而没有进行类型检查。查看
callCC
的类型,它是
((a->conttrmb)->conttrma)->conttrma
。如果我们试图返回continuation,我们将返回类型为
conttrm(a->conttrmb)
的内容。这意味着我们得到类型相等约束
a~(a->conttrmb)
,这意味着
a
必须是无限类型。Haskell不允许这样做(一般来说,有充分的理由——据我所知,这里的无限类型是类似的,提供一个无限阶函数作为参数)

在第二个例子中,你没有提到你是否有什么困惑,但是。它不打印“uh oh…”的原因是由
k()
生成的
ContT
操作与许多
ContT
操作不同,它不使用以下计算。这就是continuations和返回
ContT
操作的普通函数之间的区别(免责声明,任何函数都可以像这样返回
ContT
操作,但通常如此)。因此,当您按照
k()
向上打印或执行其他操作时,它是不相关的,因为
k()
只会放弃以下操作

第一个例子。这里的let绑定实际上只用于处理
k
的参数。但这样做可以避免无限类型。实际上,我们在let绑定中做了一些递归,这与我们之前得到的无限类型有关
f
有点像已经完成递归的延续版本

我们传递给
callCC
的这个lambda的类型是
numn=>((n->conttrmb,n)->conttrmb)->conttrmb(n->conttrmb,n)
。这与上一个示例中的无限类型问题不同,因为我们在参数方面搞砸了。通过以其他方式使用let绑定,您可以在不添加额外参数的情况下执行类似的技巧。例如:

recur :: Monad m => ContT r m (ContT r m ())
recur = callCC $ \k -> let r = k r in r >> return r

这可能不是一个解释得非常好的答案,但基本思想是直接返回延续将创建一个无限类型的问题。通过使用let绑定在传递给
callCC
的lambda内部创建一些递归,可以避免这种情况。

示例在
ContT()IO
monad中执行,monad允许继续,从而导致
()
和一些提升的
IO

type ExM a = ContT () IO a
ContT
可能是一个令人难以置信的令人困惑的单子,但我发现Haskell的等式推理是解开它的有力工具。本答案的其余部分将分几个步骤检查原始示例,每个步骤都由语法转换和纯重命名提供支持

因此,让我们首先检查
callCC
部分的类型,它最终是整个代码的核心。该块负责生成一种奇怪的元组作为其一元值

type ContAndPrev = (Int -> ExM (), Int)

getContAndPrev :: ExM ContAndPrev
getContAndPrev = callCC $ \k -> let f x = k (f, x) 
                                in return (f, 0)
这可以通过使用
(>>=)
对其进行剖切而变得更为熟悉,这正是它在实际上下文中的使用方式任何
操作都将使用它-块去糖最终将为我们创建
(>>=)

withContAndPrev :: (ContAndPrev -> ExM ()) -> ExM ()
withContAndPrev go = getContAndPrev >>= go
最后,我们可以检查它在呼叫站点中的实际外观。为了更清楚,我将对原始示例进行一点简化

flip runContT return $ do
  lift (putStrLn "alpha")
  withContAndPrev $ \(k, num) -> do
    lift $ putStrLn "beta"
    lift $ putStrLn "gamma"
    if num < 5
      then k (num + 1) >> return ()
      else lift $ print num
这仍然是一个令人头痛的问题,但可能更容易看到它是如何工作的。我们将获取do块的剩余部分,并创建一个名为
go
的循环构造。我们向块中传递一个函数,该函数使用一个新的整数参数调用我们的循环器,
go
,并返回前面的参数

我们可以通过对原始代码进行一些语法上的更改来稍微展开这段代码

maybeCont :: ContAndPrev -> ExM ()
maybeCont k n | n < 5     = k (num + 1)
              | otherwise = lift (print n)

bg :: ExM ()
bg = lift $ putStrLn "beta" >> putStrLn "gamma"

flip runContT return $ do
  lift (putStrLn "alpha")
  withContAndPrev' $ \(k, num) -> bg >> maybeCont k num
很明显,我们的伪实现重新创建了原始循环的行为。我们的伪行为是如何通过使用它作为参数接收的“do块的其余部分”进行递归连接来实现这一点的,这一点可能更为清楚

有了这些知识,我们可以更仔细地了解一下
callCC
。我们将再次通过以其预先绑定的形式对其进行初步检查而获益。这是非常简单的,如果奇怪的话,在这种形式

withCC gen block = callCC gen >>= block
withCC gen block = block (gen block)
换句话说,我们使用
callCC
gen
的参数来生成
callCC
的返回值,但我们将值应用到的延续
gen
块中。它是递归的trippy,但从字面上看很清楚-
callCC
实际上是“用当前
withContAndPrev' :: (ContAndPrev -> ExM ()) -> ExM ()
withContAndPrev' = go 0 where 
  go n next = next (\i -> go i next, n)
maybeCont :: ContAndPrev -> ExM ()
maybeCont k n | n < 5     = k (num + 1)
              | otherwise = lift (print n)

bg :: ExM ()
bg = lift $ putStrLn "beta" >> putStrLn "gamma"

flip runContT return $ do
  lift (putStrLn "alpha")
  withContAndPrev' $ \(k, num) -> bg >> maybeCont k num
let go n next = next (\i -> go i next, n)
    next      = \(k, num) -> bg >> maybeCont k num
in
  go 0 next
  (\(k, num) -> betaGam >> maybeCont k num) (\i -> go i next, 0)
  bg >> maybeCont (\i -> go i next) 0
  bg >> (\(k, num) -> betaGam >> maybeCont k num) (\i -> go i next, 1)
  bg >> bg >> maybeCont (\i -> go i next) 1
  bg >> bg >> (\(k, num) -> betaGam >> maybeCont k num) (\i -> go i next, 2)
  bg >> bg >> bg >> maybeCont (\i -> go i next) 2
  bg >> bg >> bg >> bg >> maybeCont (\i -> go i next) 3
  bg >> bg >> bg >> bg >> bg >> maybeCont (\i -> go i next) 4
  bg >> bg >> bg >> bg >> bg >> bg >> maybeCont (\i -> go i next) 5
  bg >> bg >> bg >> bg >> bg >> bg >> lift (print 5)
withCC gen block = callCC gen >>= block
withCC gen block = block (gen block)
withCC (\k -> let f x = k (f, x)
              in  return (f, 0)) next
next (let f x = next (f, x) in return (f, 0))
import Control.Monad.Trans.Cont
import Control.Monad.Trans.Class

data Mu t = In { out :: t (Mu t) }

newtype C' b a = C' { unC' :: a -> b }
type C b = Mu (C' b)

unfold = unC' . out
fold = In . C'

setjmp = callCC $ (\c -> return $ fold c)
jump l = unfold l l

test :: ContT () IO ()
test = do
    lift $ putStrLn "Start"
    l <- setjmp
    lift $ putStrLn "x"
    jump l

main = runContT test return