10

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

示例用法computationmonad 表达了不同的可能选择(在这种情况下,是要打印的东西)。该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"
---

问题。有没有一种干净的方法可以使用某种延续传递风格的单子变压器来实现上述行为?我查看了 Oleg 等人的“Backtracking, Interleaving, and Terminating Monad Transformers”论文,但似乎无法完全掌握它们的实现(一旦他们msplit使用延续实现)。

当前实施。我当前的实现是传入要做出的分支决策列表。monad 将返回它实际选择的分支列表,然后下次我们将切换最后一个可能的分支。代码如下(应该在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)
4

3 回答 3

8

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

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

然而,它是Applicative。我们需要的类型是[IO a],它是 2 个应用函子的组合,所以我们可以Data.Functor.Compose从 transformers 包中使用。这也提供了一个免费的Alternative实例<|>。我们将使用 Rebindable Syntax 来为 Applicatives 使用 do-notation:

{-# 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
于 2011-12-06T11:16:01.293 回答
3

到目前为止你得到的建议不起作用。这是怎么回事:

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"

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

于 2011-12-05T10:27:42.273 回答
1

如果您正在寻找一种基于延续的方法,那么您不会比论文中SFKT成功/失败延续实现简单得多。LogicT

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

更新:正如 Sjoerd Visscher 指出的那样,这是不对的,因为重新启动只发生在mplus而不是整个计算中。这是一个比初读时要复杂得多的问题。

于 2011-12-05T06:39:09.733 回答