【问题标题】:Using Cont to acquire values from the future and the past使用 Cont 从未来和过去获取值
【发布时间】:2012-05-20 13:37:20
【问题描述】:

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

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

但是,将脑残程序的文本表示解析为这种数据类型是很棘手的。尝试正确解析方括号时会出现问题,因为需要打结,以便循环内的最终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 的布尔值颠倒了。

注意 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 来完成。你能告诉我怎么做吗?

【问题讨论】:

  • 使用 Sentinel 捕捉未来过去几天的突变体。
  • 说真的,你还需要使用Cont吗?难道你不能只计算要跳转的指令数,也许做一个多遍解析,其中额外的遍将要跳转的指令数(连同方向)与'['']' 字符相关联?也就是说,[Char] -&gt; [JumpInfo Char] 然后在结果上使用你的解析器,其中data JumpInfo = JumpInfo Char (Maybe Integer)
  • 或者,您可以保持数据不变(或者大多数情况下,没有投入太多脑力),仍然使用 1-pass 解析器,并通过在运行时,您可以在其中反复移动磁带,直到到达目的地。虽然这会奏效,但这将是一个缓慢的跳跃。
  • @trinithis 当然,但我对了解 Cont 的兴趣远大于我对实现这个特定解释器的兴趣。
  • 如果在遇到[ 之前遇到] 会发生什么?

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


【解决方案1】:

带有 continuation monad 的前进状态如下所示:

Cont (fw -> r) a

那么cont的参数类型是

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

所以你得到一个从过去传入的fw,你必须将它传递给延续。

倒车状态如下:

Cont (bw, r) a

那么cont的参数类型是

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

即你从你必须传递给过去的延续中得到一个bw

这些可以组合成一个延续单子:

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)

【讨论】:

  • 我终于开始重构我的代码,这样我就可以将 MonadFix 和 Cont 实现保留在存储库中。我现在可以正式确认您的实现通过了“hello world”示例。我做了一些细微的调整,以便在左大括号和右大括号处共享 loopControl。 github.com/DanBurton/bf-interp/blob/master/ParseMonad/Cont.hs
  • 是的,我在发布之前测试了代码。 (这是我至少可以为 250 加分做的事情。)我做了一件好事,否则我将无法看到 toProgramStep 反向构建程序。和不错的调整!
【解决方案2】:

由于我对Cont 不满意,因此对这个答案非常懒惰,但是MonadFix 可能是您要找的吗? State 是一个实例,虽然不是Cont,它可以让你做一些看起来像的事情(使用"recursive do" notation):

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

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

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

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

以上希望能给你一些想法。

【讨论】:

  • 我最初尝试使用State 解决方案,我知道存在MonadFix 之类的东西,但我找不到它。我一定会在接下来的几天里研究它,让你知道它对我的效果。
  • 有兴趣了解您的发现。 FWIW 看起来MonadCont 的许多实例也是MonadFix
  • Here's what I've got,感觉不错,但仍然产生错误输出。我将不得不更深入地了解原因。
  • 有趣的是,Cont 版本和 MonadFix 版本的输出是相同的。所以要么他们两个都错了,要么他们都是正确的,问题出在其他地方。
  • 我修好了!转到错误的循环分支时出现问题。 Cont 版本在更改loopControl 组合子后仍然出现此问题,而 MonadFix 版本没有,所以我的Cont 代码仍然存在问题。但是 MonadFix 版本有效!感谢您的建议。
猜你喜欢
  • 2015-03-22
  • 1970-01-01
  • 2013-12-05
  • 1970-01-01
  • 1970-01-01
  • 2020-05-09
  • 1970-01-01
  • 2016-08-09
  • 1970-01-01
相关资源
最近更新 更多