Haskellでチューリングマシン(3) モナドを使ってどうするか

チューリングマシンを作るに当たって、テープを操作する処理をモナドにするというのを試みたわけですが、もとよりモナドの使い方がよく解っていないため改めて見返すとなかなか酷い出来です。

結局、自分でもしっくり収まっていなかったのでしばらく停滞していましたが、チマチマ勉強を進めたおかげで、ようやく方向性が見えてきました。
って、もったいぶるような事じゃないですね、実のところ前回のStateモナド再発明はそのための前置きだったんですけど。



というわけで、改めて考えましょう、テープの定義はこうでした。

--テープの値
data TapeValue = T0 | T1 deriving Eq
instance Show TapeValue where
  show T0 = "0"
  show T1 = "1"

--無限長の長さを持つテープ
data Tape = Tape [TapeValue] [TapeValue] deriving Eq
instance Show Tape where
  show (Tape front forward) =
    toStr (reverse front) ++ "|" ++ show (head forward) ++ "| " ++ toStr (tail forward)
    where toStr l = concat $ map (\v -> show v ++ " ") l

で、こんな感じで意味のないデータ型を作って無理やりモナドと名付けていたのですが・・・

--チューリングマシンモナド 
data TuringMachine a = TuringMachine a deriving (Show,Eq)
instance Monad TuringMachine where
  return a = TuringMachine a
  (TuringMachine x) >>= f = f x

チューリングマシンの「状態」はテープの移動や読み書きで変わります、ヘッドには常にステータスが保持されています。なのでこれらはStateモナドと同じ容量で水面下でもち回すことにしましょう。
次のように書き直します。

--ヘッドの状態 (テープ、ステータス)
data MachineState = MachineState Tape Int deriving Show

--チューリングマシンモナド Stateモナドと同様の仕組みで MachineState を保持する
newtype Machine a = Machine { runMachine :: MachineState -> (a,MachineState) }
instance Functor Machine where
  fmap f m = Machine $ \s -> let
    (a,s') = runMachine m s
    in (f a,s')
instance Monad Machine where
  return a = Machine $ \s -> (a,s)
  m >>= k = Machine $ \s -> let
    (a,s') = runMachine m s
    in runMachine (k a) s'

とりあえず動かすことに注視してたのでこのブログではあまり触れていなかったのですが、モナドを語るためにはすごく重要なFunctorという型クラスもここではちゃんと定義してやります。
GeneralizedNewtypeDerivingで簡潔にできると思いますが、とりあえず後にしておきましょう。型の関係で、できませんでした。仕方ないのでボイラープレート書きます。

処理開始時にテープが初期化されている事を保証するため、rumMachineフィールドラベルは使わず(エクスポートせず)、次のようなstartMachine関数を定義します。

getTape関数はMachineStateからテープのみ取り出す関数ですが・・・フィールドで定義したほうが良かったかな・・・

--モナドを使った処理の開始
startMachine :: Machine a -> Tape
startMachine f = getTape.snd.(runMachine f) $ MachineState (Tape [] [T0]) 0

いくつかの操作用関数を書いたのですが、ここではとりあえずエクスポート文だけ掲載。それなりの規模になってきたので、プログラム全体は最後に掲載します。

module Machine(
  --テープの値 T0 または T1
  TapeValue(..),
  --Machineモナド開始関数
  startMachine,
  --ヘッドの移動
  moveFront,
  moveForward,
  --テープ値操作
  writeTape,
  readTape,
  --ヘッドの値操作
  writeState,
  readState
  ) where



これで、モナド内で常にチューリングマシンの状態が保持される事が保証されました。さっそく試してみましょう。

module Main where
import Control.Monad(replicateM_)
import Machine 

test1 = do
  moveFront
  writeTape T1
  moveFront 

test2 = do
  moveFront >> moveFront
  writeTape T1
  tmp <- readTape
  moveForward >> moveForward 
  writeTape tmp
  moveForward >> moveForward
  writeTape tmp
  moveFront >> moveFront
  writeTape T0
  
test3 = do
  replicateM_ 10 moveFront
  replicateM_ 5 moveForward
  writeTape T1

main = do
  putStrLn.show $ startMachine test1
  putStrLn.show $ startMachine test2
  putStrLn.show $ startMachine test3

実行結果:

$ runghc Main.hs
|0| 1 0 
1 0 |0| 0 1 
0 0 0 0 0 |1| 0 0 0 0 0 

テープの移動や読み書きはバッチリですね。

ヘッドのステータスが正しく読み書きできているかどうかは、ghciで確認できます。

*Machine> let init = MachineState (Tape [] [T0]) 0
*Machine> runMachine (do{ writeState 1; }) init
((),MachineState |0|  1)
*Machine> runMachine (do{ writeState 1; x <- readState; return x }) init
(1,MachineState |0|  1)
*Machine> runMachine (do{ writeState 1; x <- readState; writeState 10; return x }) init
(1,MachineState |0|  10)



今後は、入出力と併用できるようにモナド変換子にする事も考えなくてはいけないので、先はまだまだ長いです。というかそもそもモナド変換子の仕組みがまだよく解っていないので、まずは使えるようにする所からですね。

Machine.hs 全体:

module Machine(
  --テープの値 T0 または T1
  TapeValue(..),
  --Machineモナド開始関数
  startMachine,
  --ヘッドの移動
  moveFront,
  moveForward,
  --テープ値操作
  writeTape,
  readTape,
  --ヘッドの値操作
  writeState,
  readState
  ) where

------------------------------------------------
-- データ定義 
------------------------------------------------
--テープの値
data TapeValue = T0 | T1 deriving Eq
instance Show TapeValue where
  show T0 = "0"
  show T1 = "1"

--無限長の長さを持つテープ
data Tape = Tape [TapeValue] [TapeValue] deriving Eq
instance Show Tape where
  show (Tape front forward) = 
    toStr (reverse front) ++ "|" ++ show (head forward) ++ "| " ++ toStr (tail forward)
    where toStr l = concat $ map (\v -> show v ++ " ") l

--ヘッドの状態 (テープ、ステータス)
data MachineState = MachineState { msTape :: Tape, msState :: Int } deriving Show

--チューリングマシンモナド Stateモナドと同様の仕組みで MachineState を保持する
newtype Machine a = Machine { runMachine :: MachineState -> (a,MachineState) }
instance Functor Machine where
  fmap f m = Machine $ \s -> let
    (a,s') = runMachine m s
    in (f a,s')
instance Monad Machine where
  return a = Machine $ \s -> (a,s)
  m >>= k = Machine $ \s -> let
    (a,s') = runMachine m s
    in runMachine (k a) s'

--モナドを使った処理の開始
startMachine :: Machine a -> Tape
startMachine f = msTape.snd.(runMachine f) $ MachineState (Tape [] [T0]) 0

------------------------------------------------
-- 状態操作用関数 
------------------------------------------------
--テープ移動
moveFront :: Machine ()
moveFront = Machine $ \s -> ((),mapMachine mFront s)
  where mFront (Tape [] forward) = Tape [] (T0 : forward)
        mFront (Tape front forward) = Tape (tail front) (head front : forward)

moveForward :: Machine ()
moveForward = Machine $ \s -> ((),mapMachine mForward s)
  where mForward (Tape front (position : [])) = Tape (position : front) [T0]
        mForward (Tape front forward) = Tape (head forward : front) (tail forward)

--読み書き
writeTape :: TapeValue -> Machine ()
writeTape v = Machine $ \s -> ((),mapMachine (wTape v) s)
  where wTape val (Tape front forward) = Tape front (val : tail forward)

readTape :: Machine TapeValue 
readTape = Machine $ \s -> (rTape $ msTape s,s)
  where rTape (Tape _ v) = head $ v

--ステータスの読み書き
writeState :: Int -> Machine ()
writeState v = Machine $ \s -> ((),seted s)
  where seted (MachineState t _) = MachineState t v 

readState :: Machine Int
readState = Machine $ \s -> (getState s,s)
  where getState (MachineState _ v) = v 

-------- 補助
mapMachine :: (Tape -> Tape) -> MachineState -> MachineState 
mapMachine f (MachineState t s) = MachineState (f t) s

2012/01/29 リファクタリングしたものに差し替えました