[Haskell]BrainFuckモナド

Haskell 内で DSL として Brainfuck を書きたくなる機会があったためモナドとして実装しました。

# BrainFuck.hs

```hs:BrainFuck.hs
{-# LANGUAGE GADTs #-}
module BrainFuck where
import Control.Monad.Operational
import Data.Word

data BFInst a where
    Forward :: BFInst ()
    Backward :: BFInst ()
    Incr :: BFInst ()
    Decr :: BFInst ()
    Print :: BFInst ()
    Get :: BFInst ()
    Repeat :: BrainFuck () -> BFInst ()

type BrainFuck a = Program BFInst a
data Memory = Memory {left :: [Word8], point :: Word8, right :: [Word8]}

forward_ :: Memory -> Memory
forward_ (Memory l p (r:rs)) = Memory (p:l) r rs

backward_ :: Memory -> Memory
backward_ (Memory (l:ls) p r) = Memory ls l (p:r)

incr_ :: Memory -> Memory
incr_ m = m{point = point m + 1}

decr_ :: Memory -> Memory
decr_ m = m{point = point m - 1}

runBF :: BrainFuck a -> Memory -> IO (a, Memory)
runBF m = case view m of
    Return a -> (\st -> return (a, st))
    Forward :>>= k -> \st -> runBF (k ()) $ forward_ st
    Backward :>>= k -> \st -> runBF (k ()) $ backward_ st
    Incr :>>= k -> \st -> runBF (k ()) $ incr_ st
    Decr :>>= k -> \st -> runBF (k ()) $ decr_ st
    Print :>>= k -> \st -> putWord8 (point st) >> runBF (k ()) st
    Get :>>= k -> \st -> do
        w <- getWord8
        runBF (k ()) st{point = w}
    Repeat bf :>>= k -> \st -> do
        let loop st' = do
                if point st' == 0 then
                    return st'
                else do
                    (_, st'') <- runBF bf st'
                    loop st''
        st' <- loop st
        runBF (k ()) st'

executeBF :: BrainFuck a -> IO (a, Memory)
executeBF bf = runBF bf emptyMemory

emptyMemory :: Memory
emptyMemory = Memory (repeat 0) 0 (repeat 0)

putWord8 :: Word8 -> IO ()
putWord8 = putChar . toEnum . fromEnum

getWord8 :: IO Word8
getWord8 = fmap (toEnum . fromEnum) getChar


fwd :: BrainFuck ()
fwd = singleton Forward

bwd :: BrainFuck ()
bwd = singleton Backward

inc :: BrainFuck ()
inc = singleton Incr

dec :: BrainFuck ()
dec = singleton Decr

prt :: BrainFuck ()
prt = singleton Print

get :: BrainFuck ()
get = singleton Get

rpt :: BrainFuck () -> BrainFuck ()
rpt bf = singleton $ Repeat bf
```

# サンプルコード

| Brainfuck   | BrainFuckモナド |
|:-:|:-:|
| + | inc |
| - | dec |
| > | fwd |
| < | bwd |
| . | prt |
| , | get |
| [] | rpt |

元のコード
> +++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+++.>-.------------.<++++++++.--------.+++.------.--------.>+.

```hs:Main.hs
module Main where

import BrainFuck

main :: IO ()
main = do
    executeBF hello
    return ()

hello :: BrainFuck ()
hello = do
    inc
    inc
    inc
    inc
    inc
    inc
    inc
    inc
    inc
    rpt $ do
        fwd
        inc
        inc
        inc
        inc
        inc
        inc
        inc
        inc
        fwd
        inc
        inc
        inc
        inc
        inc
        inc
        inc
        inc
        inc
        inc
        inc
        fwd
        inc
        inc
        inc
        inc
        inc
        bwd
        bwd
        bwd
        dec
    fwd
    prt
    fwd
    inc
    inc
    prt
    inc
    inc
    inc
    inc
    inc
    inc
    inc
    prt
    prt
    inc
    inc
    inc
    prt
    fwd
    dec
    prt
    dec
    dec
    dec
    dec
    dec
    dec
    dec
    dec
    dec
    dec
    dec
    dec
    prt
    bwd
    inc
    inc
    inc
    inc
    inc
    inc
    inc
    inc
    prt
    dec
    dec
    dec
    dec
    dec
    dec
    dec
    dec
    prt
    inc
    inc
    inc
    prt
    dec
    dec
    dec
    dec
    dec
    dec
    prt
    dec
    dec
    dec
    dec
    dec
    dec
    dec
    dec
    prt
    fwd
    inc
    prt
```

```:出力
Hello, world!
```

やはり、元のコードに比べてインデントのおかげで内容が見やすくなっていますね。

Brainfuck でありながら Haskell の厳格な型チェックが得られるという点も非常に大きな魅力なのではないでしょうか。

皆様も Haskell 内で Brainfuck が書きたくなったときはご活用ください。