IO Monad を自分で作る

Menu Menu

IO 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 Char

top 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 を作るのに使われている。

例題 State.hs


Shinji KONO / Tue May 31 17:30:19 2011