这是对 ContT 的适当使用吗?

Is this an appropriate use of ContT?

我正在从事一个需要我编写一个小型解释器的项目。这些指令具有简单的树结构,其中一个命令具有停止执行的效果。所以在下面的例子中,"baz" 永远不会被打印出来。

import Control.Monad.Cont

data Instruction = Print String | Halt | Block [Instruction]
    deriving (Eq, Show)

instructions =
  [ Print "foo"
  , Block
    [ Print "bar"
    , Halt
    ]
  , Print "baz"
  ]

main :: IO ()
main = runContT (callCC $ interpret instructions)
                (const $ pure ())

interpret []     k = pure ()
interpret (a:as) k = case a of
    Print str -> liftIO (putStrLn str) >> interpret as k
    Block ins -> interpret ins k       >> interpret as k
    Halt      -> k ()

这是我第一次在我的一个项目中看到 ContT 的潜在用途。我想知道这是否适合使用它,或者是否有我可能忽略的更简单的解决方案。

是的,这看起来正是 Control.Monad.Cont 适合的用例类型。

您几乎肯定知道这一点,但对于其他读者来说,值得说明的是,如果您编写了 interpret,那么它是一个从指令列表到 [=13= 的函数] 像这样:

main :: IO ()
main = interpret instructions

interpret :: [Instruction] -> IO ()
interpret []     = pure ()
interpret (a:as) = case a of
    Print str -> putStrLn str >> interpret as
    Block ins -> interpret ins >> interpret as
    Halt      -> pure ()

然后 foo barbaz 都会打印出来。您需要的是一种转义机制,允许您中止整个计算并立即 return 一个值。这正是 callCC 提供的。调用命名计算(k 在你的代码中)允许你逃避整个计算,而不仅仅是 level/layer.

太棒了,我相信您在这里找到了 ContT 的合适用例。