使用Cont从未来和过去获取值

时间:2012-05-12 00:35:52

标签: haskell monads continuations tying-the-knot monadfix

我正在Haskell写一个brainfuck解释器,我想出了一个我认为对程序非常有趣的描述:

data Program m = Instruction (m ()) (Program m)
               | Control (m (Program m))
               | Halt

然而,将brainfuck程序的文本表示解析为此数据类型是很棘手的。尝试正确解析方括号时会出现问题,因为有一些结点可以使循环内的最终Instruction再次链接到循环的Control

更多初步信息。有关所有详细信息,请参阅this version on the github repo

type TapeM = StateT Tape IO
type TapeP = Program TapeM
type TapeC = Cont TapeP

branch :: Monad m => m Bool -> Program m -> Program m -> Program m
branch cond trueBranch falseBranch =
  Control ((\b -> if b then trueBranch else falseBranch) `liftM` cond)

loopControl :: TapeP -> TapeP -> TapeP
loopControl = branch (not <$> is0)

这是我试过的:

toProgram :: String -> TapeP
toProgram = (`runCont` id) . toProgramStep

liftI :: TapeM () -> String -> TapeC TapeP
liftI i cs = Instruction i <$> toProgramStep cs

toProgramStep :: String -> TapeC TapeP
toProgramStep ('>':cs) = liftI right cs
-- similarly for other instructions
toProgramStep ('[':cs) = push (toProgramStep cs)
toProgramStep (']':cs) = pop (toProgramStep cs)

push :: TapeC TapeP -> TapeC TapeP
push mcontinue = do
  continue <- mcontinue
  cont (\breakMake -> loopControl continue (breakMake continue))

pop :: TapeC TapeP -> TapeC TapeP
pop mbreak = do
  break <- mbreak
  cont (\continueMake -> loopControl (continueMake break) break)

我认为我可以某种方式使用延续来将'['案例中的信息传递给']'案例,反之亦然,但我对Cont实际上做任何事都没有足够的把握。除了如上所述pushpop之外,还可以对可能有效的东西进行猜测。这编译并运行,但结果是垃圾。

可以Cont使用这种情况适当打结吗?如果没有,那么我应该使用什么技术来实现toProgram


注1:我之前有一个微妙的逻辑错误:loopControl = branch is0让Bools逆转。

注2:我设法使用MonadFix(由jberryman建议)和State来提出解决方案(请参阅the current state of the github repository)。我仍然想知道如何使用Cont来完成此操作。

注3:我的Racketeer导师将a similar Racket program放在一起(请参阅所有修订版)。可以使用Cont将他的管道/管道输出技术转换为Haskell吗?


tl; dr 我设法使用MonadFix做到了这一点,其他人设法使用Racket的延续组合器来做到这一点。我很确定这可以在Haskell中使用Cont完成。你能告诉我怎么样吗?

2 个答案:

答案 0 :(得分:14)

带有延续monad的前进旅行状态如下:

Cont (fw -> r) a

然后cont的参数类型是

(a -> fw -> r) -> fw -> r

所以你从过去传来fw,你必须传递给续集。

向后旅行状态如下:

Cont (bw, r) a

然后cont的参数类型是

(a -> (bw, r)) -> (bw, r)

即。你从延续中获得bw,你必须传递给过去。

这些可以合并为一个延续monad:

Cont (fw -> (bw, r)) a

将此应用到解析器时有一个问题,因为toProgramStep反向构建程序,因此']'点列表是正向状态,'''点列表是后向状态。此外,我懒惰并跳过了Maybe部分,它应该捕捉openBracecloseBrace中的模式匹配错误。

type ParseState = Cont ([TapeP] -> ([TapeP], TapeP))

toProgram :: String -> TapeP
toProgram = snd . ($ []) . (`runCont` (\a _ -> ([], a))) . toProgramStep


openBrace :: ParseState TapeP -> ParseState TapeP
openBrace mcontinue = do
  continue <- mcontinue
  cont $ \k (break:bs) -> let (cs, r) = k (loopControl continue break) bs in (continue:cs, r)

closeBrace :: ParseState TapeP -> ParseState TapeP
closeBrace mbreak = do
  break <- mbreak
  cont $ \k bs -> let (continue:cs, r) = k (loopControl continue break) (break:bs) in (cs, r)

答案 1 :(得分:4)

因为我对Cont感到不舒服而对这个答案非常懒惰,但MonadFix或许正在寻找什么? State是一个实例,但不是Cont,它可以让您执行类似的操作(使用"recursive do" notation):

{-# LANGUAGE DoRec #-}
parseInst str = do
    rec ctl <- parseInstructionsLinkingTo ctl str

这是我为我的actor库发现的解决方案:我们想要一个spawn操作来返回生成的actor的邮箱,但是我们如何才能启动相互通信的actor?或者是可以访问自己邮箱的演员?

我们可以使用合适的MonadFix实例:

fork3 = do
    rec mb1 <- spawn $ actorSpamming mb2 mb3
        mb2 <- spawn $ actorSpamming mb1 mb2
        mb3 <- spawn $ actorSpamming mb2 mb3
    send "go" mb1

希望上面给你的想法。