逃离Continuation monad中的IO monad

时间:2011-07-06 13:23:09

标签: haskell monads continuations

令人困惑的问题令人困惑的标题!我理解a)monad,b)IO monad,c)Cont monad(Control.Monad.Cont),和d)ContT连续变换器monad。 (而且我模糊地理解monad变换器 - 尽管还不足以回答这个问题。)我理解如何编写一个程序,其中 all 函数在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

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

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

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

bar :: Int -> Cont r Int
bar m = return $ m * 2

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

3 个答案:

答案 0 :(得分:17)

这就是Control.Monad.Class的用武之地。让bar多态符合它可以使用的monad:

bar :: MonadCont m => Int -> m Int
bar m = return $ m * 2

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

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

答案 1 :(得分:5)

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

liftCont :: Cont (m r) a -> ContT r m a
liftCont = ContT . runCont

这会解压缩Cont并构建ContT

然后,我可以使用liftContBar致电Foo

foo n = do
    let x = n + 1
    liftIO $ print x
    liftCont $ bar x

我不认为这比卡尔的解决方案“更好”(我给了他勾号),但是我在这里发布了它,因为它可以让你使用Bar而无需修改它的类型,如果可以的话,这非常有用修改Bar。 (虽然它的表现可能更差。)

答案 2 :(得分:1)

另一个选择是考虑mmorph包 https://hackage.haskell.org/package/mmorph-1.0.0/docs/Control-Monad-Morph.html#v:hoist

在教程部分中,查看hoist可以执行的操作。