我怎样才能用延续来实现这个 monad 转换器?

发布于 2024-12-19 06:55:08 字数 1618 浏览 4 评论 0原文

动机。我正在尝试创建一个 monad 转换器,并使用特殊指令 f <||> g 表示“重复包含 f <||> g 的整个块,一次使用 f,下一次使用 g代码>”。这旨在用于 DSL 转换,但您可以想象其他应用程序。

示例用法computation monad 表达了不同的可能选择(在本例中,是要打印的内容)。 printme 函数说明如何处理每个不同的结果。在这种情况下,我们在运行之前打印“开始计算”,在运行之后打印“---”。

computation = do
    lift (print "start -- always")
    (lift (print "first choice") <||> lift (print "second choice"))
    lift (print "intermediate -- always")
    (lift (print "third choice") <||> lift (print "fourth choice"))
    lift (print "end -- always")

printme x = do
    putStrLn "=== start computation"
    xv <- x
    putStrLn "---\n"
    return xv

test = runIndep printme computation

输出如下,

=== start computation
"start -- always"
"first choice"
"intermediate -- always"
"third choice"
"end -- always"
---

=== start computation
"start -- always"
"first choice"
"intermediate -- always"
"fourth choice"
"end -- always"
---

=== start computation
"start -- always"
"second choice"
"intermediate -- always"
"third choice"
"end -- always"
---

=== start computation
"start -- always"
"second choice"
"intermediate -- always"
"fourth choice"
"end -- always"
---

问题。有没有一种干净的方法来使用某种连续传递风格的 monad 转换器来实现上述行为?我看过 Oleg 等人的“回溯、交错和终止 Monad Transformers”论文,但似乎无法完全掌握他们的实现(一旦他们到达带有延续的 msplit 实现) )。

当前实现。我当前的实现是传递要做出的分支决策列表。 monad 将返回它实际选择的分支列表,然后下次我们将切换最后一个可能的分支。代码如下(应该在7.0.3中运行),

import Control.Monad.Trans.Class

data IndepModelT 
              

motivation. I'm trying to create a monad transformer, with a special instruction f <||> g that means "repeat this entire block containing f <||> g, once with f, the next time with g". This is intended to be for a DSL transformation, though you can imagine other applications.

example usage. The computation monad expresses different possible choices (in this case, of things to print). The printme function says what to do with each different result. In this case, we print "start computation" before it runs, and "---" after.

computation = do
    lift (print "start -- always")
    (lift (print "first choice") <||> lift (print "second choice"))
    lift (print "intermediate -- always")
    (lift (print "third choice") <||> lift (print "fourth choice"))
    lift (print "end -- always")

printme x = do
    putStrLn "=== start computation"
    xv <- x
    putStrLn "---\n"
    return xv

test = runIndep printme computation

the output is as follows,

=== start computation
"start -- always"
"first choice"
"intermediate -- always"
"third choice"
"end -- always"
---

=== start computation
"start -- always"
"first choice"
"intermediate -- always"
"fourth choice"
"end -- always"
---

=== start computation
"start -- always"
"second choice"
"intermediate -- always"
"third choice"
"end -- always"
---

=== start computation
"start -- always"
"second choice"
"intermediate -- always"
"fourth choice"
"end -- always"
---

question. Is there a clean way to achieve the above behavior using some kind of continuation passing style monad transformer? I've looked at Oleg et al.'s "Backtracking, Interleaving, and Terminating Monad Transformers" paper, but can't seem to fully grasp their implementation (once they get to the msplit implementation with continuations).

current implementation. My current implementation is to pass in a list of branching decisions to be made. The monad will return the a list of the branches it actually chooses, and then next time we'll switch the last possible branch. The code is as follows (should run in 7.0.3),

import Control.Monad.Trans.Class

data IndepModelT ???? α = IndepModelT {
    unIndepModelT :: [Bool] -> ???? (α, [Bool]) }

instance Monad ???? => Monad (IndepModelT ????) where
    return x = IndepModelT $ \choices -> return (x, [])
    (IndepModelT x) >>= f = IndepModelT $ \choices -> do
        (xv, branches) <- x choices
        let choices' = drop (length branches) choices
        (fxv, branches') <- unIndepModelT (f xv) choices'
        return (fxv, branches ++ branches')

instance MonadTrans IndepModelT where
    lift x = IndepModelT $ \c -> liftWithChoice [] x
liftWithChoice cs mx = mx >>= \xv -> return (xv, cs)

(<||>)
  :: Monad ???? => IndepModelT ???? α -> IndepModelT ???? α -> IndepModelT ???? α
(IndepModelT f) <||> (IndepModelT g) = IndepModelT go where
    go (False:cs) = do
        (fv, branches) <- f cs
        return (fv, False : branches)
    go (True:cs) = do
        (fv, branches) <- g cs
        return (fv, True : branches)

run_inner next_choices k comp@(IndepModelT comp_inner) = do
    (xv, branches) <- k $ comp_inner next_choices
    case (get_next_choices branches) of
        Nothing -> return ()
        Just choices -> run_inner (choices ++ repeat False) k comp
    where
        get_next_choices [] = Nothing
        get_next_choices [True] = Nothing
        get_next_choices [False] = Just [True]
        get_next_choices (c:cs)
            | Just cs' <- get_next_choices cs = Just $ c:cs'
            | c Prelude.== False = Just [True]
            | otherwise = Nothing

runIndep :: Monad ???? =>
    (???? (α, [Bool]) -> ???? (β, [Bool]))
    -> IndepModelT ???? α
    -> ???? ()
runIndep = run_inner (repeat False)

runIndepFirst (IndepModelT comp) = comp (repeat False)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

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

发布评论

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

评论(3

我的奇迹 2024-12-26 06:55:09

问题是:这不是一个 monad!该行为甚至没有明确定义。 Fe 在这种情况下应该做什么:

do
  b <- ...randomly True or False...
  if b then ...some choices... else ...some other choices...

但是,它是Applicative。我们需要的类型是[IO a],它是2个applicative functor的组合,所以我们可以使用Data.Functor.Compose 来自变压器包。这也免费提供了带有 <|>Alternative 实例。我们将使用可重新绑定语法来使用 Applicatives 的 do 表示法:

{-# LANGUAGE RebindableSyntax #-}
import Prelude hiding ((>>), (>>=))
import Control.Applicative
import Data.Functor.Compose

lift :: Applicative f => g a -> Compose f g a
lift = Compose . pure

(>>) :: Applicative f => f a -> f b -> f b
(>>) = (*>)

computation :: Alternative f => Compose f IO ()
computation = do
    lift (print "start -- always")
    lift (print "first choice") <|> lift (print "second choice")
    lift (print "intermediate -- always")
    lift (print "third choice") <|> lift (print "fourth choice")
    lift (print "end -- always")

printme x = do
    putStrLn "=== start computation"
    x
    putStrLn "---\n"

test = mapM printme $ getCompose computation

Here's the problem: this is not a monad! The behavior isn't even well-defined. F.e. what should it do in this case:

do
  b <- ...randomly True or False...
  if b then ...some choices... else ...some other choices...

However, it is Applicative. The type we need is [IO a], which is the composition of 2 applicative functors, so we can use Data.Functor.Compose from the transformers package. This gives an Alternative instance with <|> for free as well. We'll use Rebindable Syntax to use the do-notation for Applicatives:

{-# LANGUAGE RebindableSyntax #-}
import Prelude hiding ((>>), (>>=))
import Control.Applicative
import Data.Functor.Compose

lift :: Applicative f => g a -> Compose f g a
lift = Compose . pure

(>>) :: Applicative f => f a -> f b -> f b
(>>) = (*>)

computation :: Alternative f => Compose f IO ()
computation = do
    lift (print "start -- always")
    lift (print "first choice") <|> lift (print "second choice")
    lift (print "intermediate -- always")
    lift (print "third choice") <|> lift (print "fourth choice")
    lift (print "end -- always")

printme x = do
    putStrLn "=== start computation"
    x
    putStrLn "---\n"

test = mapM printme $ getCompose computation
水中月 2024-12-26 06:55:09

到目前为止您收到的建议不起作用。这是如何进行的:

f <||> g = ContT $ \k -> do
  xs <- runContT f k
  ys <- runContT g k
  return $ xs ++ ys

test = runContT computation (return . (:[]))

但这不会为每个选择重新启动整个计算,结果是这样的:

"start -- always"
"first choice"
"intermediate -- always"
"third choice"
"end -- always"
"fourth choice"
"end -- always"
"second choice"
"intermediate -- always"
"third choice"
"end -- always"
"fourth choice"
"end -- always"

我还没有找到一个好的解决方案。

The suggestion you've gotten so far don't work. Here's how that would go:

f <||> g = ContT $ \k -> do
  xs <- runContT f k
  ys <- runContT g k
  return $ xs ++ ys

test = runContT computation (return . (:[]))

But that doesn't restart the whole computation for each choice, the result is this:

"start -- always"
"first choice"
"intermediate -- always"
"third choice"
"end -- always"
"fourth choice"
"end -- always"
"second choice"
"intermediate -- always"
"third choice"
"end -- always"
"fourth choice"
"end -- always"

I haven't found a good solution yet.

极度宠爱 2024-12-26 06:55:09

如果您专门寻找基于延续的方法,那么您不会比 LogicT 论文

如果 msplit 太多(而且它是一个非常微妙的野兽),您可以在此应用程序中忽略它。其目的是允许公平的结合和析取,如果这些示例输出行要按顺序打印,那么这不是您的规范的一部分。只需关注 5.1 节中的 MonadMonadPlus 实现即可。

更新:正如 Sjoerd Visscher 指出的那样,这是不对的,因为重新启动仅发生在 mplus 中,而不是整个计算中。这是一个比乍一看要棘手得多的问题。

If you're looking specifically for a continuation-based approach, you're not going to get much simpler than the SFKT success/failure continuation implementation in the LogicT paper.

If msplit is too much (and it is quite a subtle beast), you can just ignore it for this application. Its purpose is to allow fair conjunction and disjunction, which isn't part of your specification if those lines of sample output are meant to print in order. Just focus on the Monad and MonadPlus implementations in section 5.1 and you'll be all set.

Update: As Sjoerd Visscher points out, this isn't right as the restarting only happens from mplus rather than the whole computation. This is a much trickier problem than it appears at first read.

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