do構文とMonad
どうもこんばんは、south37です!今日は前回の予告通り、do構文とMonadの関係について見て行きたいと思います!!
do構文
さて、do構文とは何だったか覚えているでしょうか?
確か、以前はIOの文脈で do構文 を紹介したと思います。
main = do putStrLn "Hello, what's your name?" name <- getLine putStrLn ("Hi " ++ name ++ "!")
上記のコードをコンパイルして実行すると、putStrLn
関数の働きで"Hello, ~"
という文字列が出力された後、標準入力で名前を受け取ってname
という変数に保存し、"Hi (入力された名前)"
という文字列が出力されるのでした。
この時、一行一行の処理を糊付けして一つにまとめているのが、 do構文 が果たしている重要な役割です。
では、このdo構文がMonad
とどう関係してくるのでしょうか?
Monad
ここでHaskellにおけるMonad
についても復習しておきましょう。前々回、モナドは自己関手で〜
とかいろいろ言ったかと思いますが、ぶっちゃけそれは覚えてなくても問題ありませんw。重要なのは、型クラスとして定義されているMonad
においては、return
と>>=
(バインドと読む)という関数が定義されているという事です。
例として、Maybe
において、上記の関数がどういう風に実装されているかを見てみましょう。
instance Monad Maybe where return x = Just x Nothing >>= f = Nothing Just x >>= f = f x
instance (型クラス名) (型名) where ~
という構文は、ある型を特定の型クラスのインスタンスにする時にとられる構文で、where
以降に定義が義務づけられている関数の実装を書きます。(細かい事言うとMaybe
は型では無くて、型引数を一つとる 型コンストラクタ です。型コンストラクタも型クラスのインスタンスになるのです。)
上記のコードから、return
は値を一つ受け取ってそれをJust
にくるんだ値を返す振る舞いをしている事が分かります。型で書けば、a -> Maybe a
となっています。
一方、>>=
は中置関数(二つの引数の間に置く関数。+
などと同様)として定義されており、Maybe a
型の値(Nothing
またはJust x
)とa -> Maybe b
型の関数であるf
を受け取って、Maybe b
型の値を返す振る舞いをしています。型で表せば、Maybe a -> (a -> Maybe b) -> Maybe b
となっています。
>>=
は、Monad
にくるまれた値を普通の関数(ただし出力はモナド)に 食わす 役割をします。
実は、この>>=
がdo構文と関係してきます。
>>=
でdo構文を表現する
最初に、次のようなコードを考えてみましょう。
foo :: Maybe String (Just 3) >>= (¥x -> Just (show x ++ "!"))
¥x -> (関数の実装)
というのはその場で無名関数を定義する為の構文です。x
を引数として受け取り、出力はshow x
でx
の文字列表現を生成し、"!"
と連結してからJust
でくるんだものとなっています。
Just 3
がNum a => Maybe a
型の値で、それを>>=
関数を使って¥x -> Just (show x ++ "!")
という「a
型の値を受け取ってMaybe String
型の値を返す関数」に食わせている事が分かります。出力は、"3!"
といった感じでしょうか。まあ、>>=
の機能を考えれば普通の振る舞いですね。
では、次の様に書くとどうでしょうか。
Just 3 >>= (¥x -> Just "!" >>= (¥y -> Just (show x ++ y)))
パッと見だとよく分かりませんね...じっと見ていると、無名関数が入れ子になっている事が分かります。¥x -> ~
というのはx
を引数として受け取って何らかの処理を行う関数で、その処理というのが¥y -> ~
という無名関数に>>=
を使ってJust "!"
を食わせる事です。更に、¥x -> ~
は>>=
を使ってJust 3
に対して適用されています。
このコードは、次の様に書き換える事も出来ます。
foo :: Maybe String foo = Just 3 >>= (¥x -> Just "!" >>= (¥y -> Just (show x ++ y)))
foo
という変数は何となくノリで出しただけなので気にしないでください。
重要なのは、ただ改行を入れただけで処理自体は何も変化していない事です。でも、こう書くと何となくJust 3
の3
が¥x
に結びついて、Just "!"
の"!"
が¥y
に結びついて、それが最後の行で使われている様に見えませんか?
実は実際にそのように解釈する事が出来ます。do構文を用いると、それが顕著になります。
foo = do x <- Just 3 y <- Just "!" Just (show x ++ y)
このdo構文を用いたコードは、上で書いたコードと全く同じ処理をしています。つまり、do構文というのは>>=
関数を使った処理を、より簡単に書けるようにした 糖衣構文 に過ぎなかった訳です!!
ここではMaybe
について説明をしましたが、IO
についても同様に考える事が出来ます。すなわち、
main = do name <- getLine putStrLn ("Hi " ++ name ++ "!")
というコードは、
main = getLine >>= (¥name -> putStrLn ("Hi " ++ name ++ "!"))
というコードと同じ事をしているという訳です。getLine
がIO String
型の値を返し、その値が>>=
によって¥name -> (putStrLn ~)
というString -> IO ()
型の関数に適用されています。
ちなみに、IO
における>>=
の実装は、そこそこ公式っぽいページを見ても
m >>= k = ... -- executes the I/O action m and binds the value to k's input
としか載っていませんでした。IO
は副作用を伴う概念である為、その関数はコンパイラレベルで実装されておりコードには落とし込めないのだと思います。ただ、変数へのバインドを行う点は、Maybe
と共通しているはずです。この役割の為に、>>=
はバインドと呼ばれるのです。
>>
関数
>>=
によってdo構文におけるx <- (Monad)
という表現が理解出来る事が分かりました。では、
main = do putStrLn "Hi!" putStrLn "My name is Taro!"
の様な、独立した一行一行がdo構文でまとめられているケースはどう理解したら良いのでしょうか?
実は、これは>>
関数を用いる場合の糖衣構文です。>>
は>>=
を使って次の様に定義されています。
(>>) :: (Monad m) => m a -> m b -> m b m >> n = m >>= ¥_ -> n
>>
は二つのモナド値m
とn
を受け取り、後者のモナド値n
を返します。実装としては、¥_ -> n
という引数を受け取るが何もせずにn
を返す無名関数に対して、>>=
を用いてm
を食わせます。m
の値が必要無い時に使われるのがこちらです。
IOの例で言えば、>>=
はI/Oアクションを実行してその結果を関数に食わせる役割を果たすため、
main = putStrLn "Hi!" >> (putStrLn "My name is taro!")
と書けば、putStrLn "Hi!"
の返す値は捨ててI/Oアクションだけを実行する事が出来ます。求めていた機能と一致していますね!!!
まとめ
という事で、do構文が>>=
や>>
を用いた場合の糖衣構文に過ぎない事が分かりました。やったね!!
- 作者: Miran Lipovača,田中英行,村主崇行
- 出版社/メーカー: オーム社
- 発売日: 2012/05/23
- メディア: 単行本(ソフトカバー)
- 購入: 25人 クリック: 580回
- この商品を含むブログ (52件) を見る