逃离Continuation monad中的IO monad

mgi*_*uca 9 monads continuations haskell

令人困惑的问题令人困惑的标题!我理解a)monad,b)IO monad,c)Cont monad(Control.Monad.Cont),以及d)ContT连续变换器monad.(而且我模糊地理解monad变换器 - 虽然还不足以回答这个问题.)我理解如何编写一个程序,其中所有函数都在Cont monad(Cont r a)中,我理解如何编写一个程序,其中所有的函数在组合的Cont/IO monad(ContT r IO a)中.

但我想知道如何编写一个程序,其中某些函数在组合的Cont/IO monad(ContT r IO a)中,其他函数只在Cont monad(Cont r a)中.基本上,我想以连续样式编写整个程序,但只在必要时使用IO monad(很像"常规"Haskell代码,我只在必要时使用IO monad).

例如,以非延续样式考虑这两个函数:

foo :: Int -> IO Int
foo n = do
    let x = n + 1
    print x
    return $ bar x

bar :: Int -> Int
bar m = m * 2
Run Code Online (Sandbox Code Playgroud)

请注意,foo需要IO但是bar纯粹.现在我想出了如何使用continuation monad完全编写这段代码,但我还需要通过IO连接bar:

foo :: Int -> ContT r IO Int
foo n = do
    let x = n + 1
    liftIO $ print x
    bar x

bar :: Int -> ContT r IO Int
bar m = return $ m * 2
Run Code Online (Sandbox Code Playgroud)

确实希望我的所有代码都是延续式的,但我不想在不需要它的函数上使用IO monad.基本上,我想这样定义bar:

bar :: Int -> Cont r Int
bar m = return $ m * 2
Run Code Online (Sandbox Code Playgroud)

不幸的是,我找不到从Cont r amonad函数(bar)内部调用ContT r IO amonad函数(foo)的方法.有没有办法将一个未经改造的monad"提升"为一个变形的monad?即,如何更改" bar x" 行,foo以便它可以正确调用bar :: Int -> Cont r Int

Car*_*arl 17

这就是Control.Monad.Class的用武之地.bar在它可以使用的monad中创建多态:

bar :: MonadCont m => Int -> m Int
bar m = return $ m * 2
Run Code Online (Sandbox Code Playgroud)

请注意,页面底部的实例列表显示MonadCont生成文档时已知的实例包括Cont rMonad m => ContT r m.此外,MonadCont该类定义了callCC函数,这是使用延续特性所必需的.这意味着您可以在其中使用延续的完整表现力bar,即使此示例没有.

通过这种方式,您可以编写可证明不能使用IO的函数,因为它们没有MonadIO约束,也没有明确提及它们的类型IO.但是它们是多态的,其中monad在其中工作,因此它们可以从包含IO的上下文中简单地调用.


mgi*_*uca 5

我发现这完全符合我的要求(无需更改Bar):

liftCont :: Cont (m r) a -> ContT r m a
liftCont = ContT . runCont
Run Code Online (Sandbox Code Playgroud)

这解压Cont并构造了一个ContT.

然后我可以使用liftCont呼叫Bar来自Foo:

foo n = do
    let x = n + 1
    liftIO $ print x
    liftCont $ bar x
Run Code Online (Sandbox Code Playgroud)

我不认为这比卡尔的解决方案"更好"(我给他打勾),但我在这里发布它是因为它允许你使用Bar而不修改它的类型,如果你不能修改那么有用Bar.(虽然它的表现可能更差.)