私は Haskell でブレインファック インタープリターを書いていますが、プログラムの非常に興味深い説明であると思われるものを思いつきました。
data Program m = Instruction (m ()) (Program m)
| Control (m (Program m))
| Halt
ただし、brainfuck プログラムのテキスト表現をこのデータ型に解析するのは難しいです。角かっこを正しく解析しようとすると問題が発生します。これは、ループ内の最後の部分が再びInstruction
ループにリンクするように結び目を作る必要があるためです。Control
もう少し予備的な情報。すべての詳細については、github リポジトリでこのバージョンを参照してください。
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
がありました。ブール値が逆になっていたのです。
注2:解決策を考え出すためにMonadFix
(jberrymanの提案に従って)使用することができました( githubリポジトリの現在の状態をState
参照してください)。代わりにこれをどのように行うことができるかを知りたいです。Cont
注 3: 私の Racketeer メンターは、同様の Racket プログラムをまとめてくれました (すべてのリビジョンを参照)。彼のパイプ/パイプアウト手法は、を使用して Haskell に変換できますCont
か?
tl;dr私は MonadFix を使用してこれを行うことができ、他の誰かが Racket の継続コンビネータを使用してそれを行うことができました。Cont
これはHaskellでできると確信しています。方法を教えてもらえますか?