我正在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实际上做任何事都没有足够的把握。除了如上所述push
和pop
之外,还可以对可能有效的东西进行猜测。这编译并运行,但结果是垃圾。
可以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
完成。你能告诉我怎么样吗?
答案 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部分,它应该捕捉openBrace
和closeBrace
中的模式匹配错误。
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
希望上面给你的想法。