IO Monad を自分で作る
Menu MenuIO Monad を使った Haskell の getline は以下のような感じ。
getll = do c <-getChar1 if c=='\n' then return [] else do l <- getll return (c:l)c は新しく作られる変数。つまり、\c -> ... と言う形の Syntax suger 。
この getll 、getChar1 は、システムや外界の状態、つまり文脈に依って、c や l に異なる値を代入する。
Haskell は関数型言語なので、getll 自体は常に同じ値を返す。その値は、
文脈を入力として、実行すべきコード(関数)ということになる。これは action と呼ばれる。
このSyntax Suger を取ると、do の代わりに >>= を使って
getll = getChar1 >>= ( \c -> if c=='\n' then return [] else getll >>= ( \l -> return (c:l)))となる。つまり、実行すべきコードは、lambda 式として与えられることがわかる。
getll の型
これは、
getll :: SignalState [Char]という型を持っている。SignalState が、これから作る Monad になる。
gell は、[Char] つまり文字列を返すのではなくて、[Char] を持つ SignalState というデータ型を返している。
getChar1 は、自分でこれから作るもので、
getChar1 :: SignalState Charという型を持っている。
>>= は、
SignalState * >>= ( Char -> SignalState * )と呼び出して、返値は、SignalState となる。
return は、
return: SignalState [Char]となるべきだということがわかる。
外界あるいは文脈
getChar1 が返すのは入力された文字だが、何が返って来るかは外界の状態による。それは文脈と言っても良い。
文脈を getChar1 の返値に与えると、 その文脈に依存した Char が分かり、 次の文脈が返って来ると言う風にしたい。
SginalState * が、この場合の文脈を表すデータ構造になる。
top level
文脈を getChar / getll に与える top level の関数が必要である。
runTT getChar1 "test"と使うような runTT と言う関数である。"test" が、外界の入力(を模倣したもの)である。
runTT は、文脈から今の値を取って来て、SignalState の中の値に作用させる。
runTT (GetChar f) (h:t) = runTT (f h) t (1)ここでは外界はCharのリストで表されている。h が今のキー入力、t が、将来の入力という意味になる。GetChar は SignalState の一つの状態を表すデータ型である。
f は、h に作用して SignalState を返している。つまり、
data SignalState a = GetChar (Char -> SignalState a)と定義すれば良い。
あと、return に対応するデータ型が必要である。
runTT (ReturnTeletype a) cs = a (2)となるのが良い。すると、
data SignalState a = GetChar (Char -> SignalState a) | ReturnTeletype aというように SignalState を定義すると良いことがわかる。
getChar1 を一回だけ呼び出す
getChar1 を以下のように定義しよう。
getChar1 = GetChar ReturnTeletypeつまり、getChar1 の返値は、GetChar というデータ構造で、ReturnTeletype というデータ型のコンストラクタを中身に持つ。
runTT getChar1 "test"で、't' が返って来て欲しい。runTT を(1)を使って一段評価すると、
runTT (ReturnTeletype 't') "est"となる。(2) により、't' が返って来る。
これは、getChar1 が文脈 "test" を一つ消費して、ReturnTeletype 't' と文脈 "est" になったこと意味する。
getChar1 の戻値
getChar1 は普通の関数のようには使えない。getChar1 が返すのは、文脈を消費するためのSignalState だから。
*Main> let c = getChar1 *Main> c <interactive>:1:0: No instance for (Show (SignalState Char)) arising from a use of `print' at <interactive>:1:0 Possible fix: add an instance declaration for (Show (SignalState Char)) In a stmt of a 'do' expression: print it *Main> :t c c :: SignalState Chartop level を使わないと、
*Main> runTT c "abcd" 'a'値は取って来れない。プログラム中で、これを使うには、SignalState Monad を組み合わせる演算子 >>= が必要である。
getChar1 の値を使う
getChar1 を組み合わせる Monad は、
instance Monad SignalState where return = ReturnTeletype (ReturnTeletype a) >>= g = g a (GetChar f) >>= g = GetChar (\c -> f c >>= g)と定義される。
runTT (getChar1 >>= (\c -> return c )) "abcd"と言う形で getChar1 を呼び出そう。
runTT ((GetChar ReturnTeletype)>>= (\c -> return c )) "abcd" (GetChar ReturnTeletype) >>= (\c -> return c )は、
GetChar (\d -> ReturnTeletype d >>= (\c -> return c ))となる。これは、top level により、
runTT (GetChar (\d -> ReturnTeletype d >>= (\c -> return c ))) "abcd"が、
runTT ((\d -> ReturnTeletype d >>= (\c -> return c )) 'a' ) "bcd"となり、
runTT (ReturnTeletype 'a' >>= (\c -> return c )) "bcd"となる。
runTT ((\c -> return c ) 'a' ) "bcd"で、
runTT (return 'a') "bcd" runTT (ReturnTelType 'a') "bcd"で、 'a' が返る。
getChar1 を二回呼び出す
runTT ( getChar1>>=(\c-> getChar1>>=(\d->return (c:[d]))) ) "abcd"は、
runTT (ReturnTeletype 'a' >>= (\c-> getChar1>>=(\d->return (c:[d])))) "bcd"となる。
runTT ((\c-> getChar1>>=(\d->return (c:[d]))) 'a') "bcd" runTT (getChar1>>=(\d->return ('a':[d]))) "bcd"となる。あとは、前の場合と同じになる。
runTT (ReturnTeletype 'b' >>= (\d -> return ('a':[d]) )) "cd"で、('a':['b']) つまり、"ab" が返る。この時の文脈は "cd" となる。
(ReturnTeletype a) >>= g = g aが、\c や \d に値を代入している。
(GetChar f) >>= g = GetChar (\c -> f c >>= g)は、今の文脈から ReturnTeletype 'a' >>= g を作るのに使われている。