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番目)。


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