令人困惑的问题令人困惑的标题!我理解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
?
答案 0 :(得分:17)
这就是Control.Monad.Class的用武之地。让bar
多态符合它可以使用的monad:
bar :: MonadCont m => Int -> m Int
bar m = return $ m * 2
请注意,页面底部的实例列表显示生成文档时已知的MonadCont
实例包括Cont r
和Monad 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
。
然后,我可以使用liftCont
从Bar
致电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
可以执行的操作。