从 Continuation monad 内的 IO monad 转义
一个令人困惑的问题的令人困惑的标题!我理解 a) monad,b) IO monad,c) Cont monad (Control.Monad.Cont), 和 d) ContT 延续变压器单子。 (一般来说,我对 monad 转换器有模糊的了解——尽管不足以回答这个问题。)我了解如何编写一个程序,其中所有函数都在 Cont monad 中(Cont r a< /code>),并且我了解如何编写一个程序,其中所有功能都位于组合的 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
是纯函数。现在我弄清楚了如何完全使用延续 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函数的方法(< code>bar) 来自 ContT r IO a
monad 函数 (foo
)。有什么方法可以将未转化的单子“提升”为转化的单子吗?即,如何更改 foo
中的“bar x
”行,以便它可以正确调用 bar :: Int ->继续 Int
?
A confusing title for a confusing question! I understand a) monads, b) the IO monad, c) the Cont monad (Control.Monad.Cont), and d) the ContT continuation transformer monad. (And I vaguely understand monad transformers in general -- though not enough to answer this question.) I understand how to write a program where all the functions are in the Cont monad (Cont r a
), and I understand how to write a program where all the functions are in the combined Cont/IO monad (ContT r IO a
).
But I'm wondering how I might write a program where some functions are in a combined Cont/IO monad (ContT r IO a
) and other functions are just in the Cont monad (Cont r a
). Basically, I want to write the whole program in continuation style, but only use the IO monad where necessary (much like in "regular" Haskell code, I only use the IO monad where necessary).
For example consider these two functions, in non-continuation style:
foo :: Int -> IO Int
foo n = do
let x = n + 1
print x
return $ bar x
bar :: Int -> Int
bar m = m * 2
Note that foo
requires IO but bar
is pure. Now I figured out how to write this code fully using the continuation monad, but I needed to thread IO through bar
as well:
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
I do want all my code in continuation style, but I don't want to have to use the IO monad on functions that don't require it. Basically, I would like to define bar
like this:
bar :: Int -> Cont r Int
bar m = return $ m * 2
Unfortunately, I can't find a way to call a Cont r a
monad function (bar
) from inside a ContT r IO a
monad function (foo
). Is there any way to "lift" a non-transformed monad into a transformed one? i.e., how can I change the line "bar x
" in foo
so that it can correctly call bar :: Int -> Cont r Int
?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这是 控制的地方。 Monad.Class 出现。让
bar
在它可以工作的 monad 中实现多态:请注意,页面底部的实例列表显示
MonadCont
的实例代码>已知生成文档时包括Cont r
和Monad m =>;继续T r m
。此外,MonadCont
类定义了callCC
函数,这是使用延续功能所必需的。这意味着您可以在bar
中使用延续的完整表达能力,即使本示例没有这样做。通过这种方式,您编写的函数可以证明不能使用 IO,因为它们没有
MonadIO
约束,它们的类型也没有显式提及IO
。但它们在其中工作的 monad 是多态的,因此可以从包含 IO 的上下文中简单地调用它们。This is where Control.Monad.Class comes in. Make
bar
polymorphic in what monad it can work in:Note that the instances list at the bottom of the page shows that the instances of
MonadCont
known at the time the docs were generated include bothCont r
andMonad m => ContT r m
. Additionally, theMonadCont
class is what defines thecallCC
function, which is what is necessary to use the continuation features. This means you can use the full expressiveness of continuations withinbar
, even though this example does not.In this way, you write functions that provably can't use IO, because they don't have a
MonadIO
constraint, nor does their type explicitly mentionIO
. But they are polymorphic in which monad they work within, such that they can be called trivially from contexts that do include IO.我发现这正是我想要的(无需更改
Bar
):这会解压
Cont
并构造一个ContT
。然后我可以使用
liftCont
从Foo
调用Bar
:我不认为这比 Carl 的解决方案“更好”(我给了他勾选),但我将其发布在这里是因为它允许您使用
Bar
而无需修改其类型,如果您无法修改Bar
,则非常有用。 (不过它的性能可能更差。)I found that this does exactly what I wanted (without having to change
Bar
):This unpacks the
Cont
and constructs aContT
.I can then use
liftCont
to callBar
fromFoo
:I don't think this is "nicer" than Carl's solution (I gave him the tick), but I posted it here because it lets you use
Bar
without modifying its type, so useful if you can't modifyBar
. (It probably has worse performance though.)另一种选择是考虑 mmorph 包
https://hackage.haskell .org/package/mmorph-1.0.0/docs/Control-Monad-Morph.html#v:hoist
在教程部分中,了解
hoist
可以做什么。Another option is to consider the mmorph package
https://hackage.haskell.org/package/mmorph-1.0.0/docs/Control-Monad-Morph.html#v:hoist
In the tutorial section, look at what
hoist
can do.