从 Continuation monad 内的 IO monad 转义

发布于 2024-11-18 17:43:26 字数 1617 浏览 4 评论 0原文

一个令人困惑的问题的令人困惑的标题!我理解 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

无所谓啦 2024-11-25 17:43:26

这是 控制的地方。 Monad.Class 出现。让 bar 在它可以工作的 monad 中实现多态:

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

请注意,页面底部的实例列表显示 MonadCont 的实例代码>已知生成文档时包括 Cont rMonad 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:

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

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 both Cont r and Monad m => ContT r m. Additionally, the MonadCont class is what defines the callCC function, which is what is necessary to use the continuation features. This means you can use the full expressiveness of continuations within bar, 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 mention IO. But they are polymorphic in which monad they work within, such that they can be called trivially from contexts that do include IO.

究竟谁懂我的在乎 2024-11-25 17:43:26

我发现这正是我想要的(无需更改 Bar):

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

这会解压 Cont 并构造一个 ContT

然后我可以使用 liftContFoo 调用 Bar

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

我不认为这比 Carl 的解决方案“更好”(我给了他勾选),但我将其发布在这里是因为它允许您使用 Bar 而无需修改其类型,如果您无法修改 Bar,则非常有用。 (不过它的性能可能更差。)

I found that this does exactly what I wanted (without having to change Bar):

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

This unpacks the Cont and constructs a ContT.

I can then use liftCont to call Bar from Foo:

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

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 modify Bar. (It probably has worse performance though.)

静若繁花 2024-11-25 17:43:26

另一种选择是考虑 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文