BrainF*ckインタプリタを作る(3)

入出力の命令「.」と「,」を実装した。

最初,よく考えもせずに出力する関数 bfPrint をこうした。

bfPrint bf = print $ bfValue bf

確かにこれでこの関数自体はちゃんと動く。つまり1文字出力される。

*Main> bfPrint $ bfIncrement bfInitial
Loading package haskell98-1.0 ... linking ... done.
1

けど,返り値が IO () なのであとが続かない。出力命令が来たらそこで終わり,では話しにならないよな。
ここでしばらく行き詰まってしまった。
次の命令の処理につなげるには BrainF_ck を返さないといけないけど,どうやったらいいのか。
一部の命令だけ IO モナドになってしまうのを,他の命令と型を合わせるにはどうしたらいいのか。

結局「入門Haskell―はじめて学ぶ関数型言語」のモナドの章を読み直して,何とかできたのがこれ。

  • 命令をつなげるのには IO BrainF_ck をつかう。
  • 値を返すには return をつかう。

bfEvaluate は1つの命令を評価するようにして,次々に処理するのは main に移した。モナドを扱うので foldM を使った。

bfInput :: BrainF_ck -> IO BrainF_ck
bfInput bf = do let p = bfPointer bf
                let r = bfRegister bf
                putStr "\ninput? "
                v <- getChar return $ BF p ((take p r) ++ [read [v]] ++ (tail $ drop p r))

bfPrint :: BrainF_ck -> IO BrainF_ck
bfPrint bf = do putStr $ show $ bfValue bf
                return bf

bfEvaluate :: BrainF_ck -> Char -> IO BrainF_ck
bfEvaluate bf c = case c of
                  '+' -> return $ bfIncrement bf
                  '-' -> return $ bfDecrement bf
                  '>' -> return $ bfShift bf
                  '<' -> return $ bfUnshift bf
                  '.' -> bfPrint bf
                  ',' -> bfInput bf

main :: IO ()
main = do args <- getArgs
          prog <- readFile $ head args
          result <- foldM bfEvaluate bfInitial prog
          print result

あ,foldM を使うには import Monad が必要。 さて,試してみよう。入力するプログラムはこれ。

 ++.>++.>++.<-.>>,.

結果。

>runghc hbf.hs sample.bf
2221
input? 7
7BF {bfPointer = 3, bfRegister = [2,1,2,7,0,0,0,0,0,0]}

一番最後に状態を出力してるから見にくいけどそれはおいといて。
input? のあとの 7 が入力。で,そのすぐあとに入力されたばかりの 7 を出力している。最後の状態を見てもちゃんと 7 が入力されている(左から4番目)。

というわけで,何とかできたけど入出力は難しい。これで良いのかなぁ。もっとスマートにいかないものか。