1.現実の世界
モナドは二つの演算returnと>>=を有していた。この中の演算(>>=)を連続して用いることにより、同じ型の中で、鎖のように繋がった操作(オペレーション)が可能になった。これを見やすくしたのがdo記法であった。
do記法を用いることで逐次型の処理が可能になる。逐次型の処理で特に重要なものは、入出力を伴う処理であろう。Haskellでは、入出力はIOモンドで行われる。
2.putStrとgetLine
IOモナドの関数の中で、最も基本的な関数はputStrとgetLineである。putStrはディスプレイ(標準入力)に文字列を出力するものであり、getLineは一行の文字列をキーボード(標準入力)から入力するものである。型シグネチャはそれぞれ次のようになっている。
Prelude> :t putStr putStr :: String -> IO ()
Prelude> :t getLine getLine :: IO String
さて、一行の文字列を得て、その文字列をそのまま出力するプログラムは次のようになる。
main = getLine >>= putStr
これを実行する。関数名mainを打ち込んだ後、「I like Haskell.改行(Enter)」とキーボードから打ち込むと、同じものが返ってくる(ここで、改行は一行の終わりを示すものとして使われ、getLineからの出力は、改行の前までである。この場合はピリオドまでである)。
*Main> main I like Haskell. I like Haskell.*Main>
このプログラムでは出力は改行されない。改行をしたければ、putStrをpurStrLnに変えればよい。即ち、
main = getLine >>= putStrLn
とする。もう一度、「I like Haskell.改行(Enter)」とキーボードから打ち込むと、
*Main> main I like Haskell. I like Haskell. *Main>
となり、改行されていることが分かる。
上のプログラムをdo記法で書き表すと次のようになる。
main = do x <- getLine putStrLn x
それでは、二つの数を入力してその和を出力するプログラムを考える。二つの数の和はIOモナドではないので、そのまま出力することができないが、これを助けてくれるのが関数printである。printの型シグネチャは次のようになっている。
*Main> :t print print :: Show a => a -> IO ()
これから、printは型クラスShowのインスタンスaをIOモナドに変換してくれる、ことが分かる。すべての数はShowのインスタンスであるため、数をディスプレイ上に表示したいときは、printを用いればよい。
そこで、プログラムは次のようになる。
main = do x <- getLine y <- getLine print $ read x + read y
上のプログラムで、xとyは文字列として入力されるので、文字列を数に変える必要がある。そこで、それぞれの前にreadがついている。
readの型シグネチャは次のようになっている。
read :: Read a => String -> a
実行結果は、例えば、次のようになる。
*Main> main 45 32 77
3.forMとwhen
モナドが逐次的に実行することから、JavaやCが備えている処理の流れを制御文、Haskellは関数型なのでこの場合は関数になるが、をHaskellでも備えている。それらは、whenとforMという関数である。なお、これらの関数は、Control.monadというモジュールにあるので予めimportしておく必要がある。
例によって型シグネチャを見る。whenは次のようになっている。
*Main> main *Main> import Control.Monad *Main Control.Monad> :t when when :: Monad m => Bool -> m () -> m ()
これからwhenはブールとモナドmを受け取り、モナドmを返すことが分かる。型シグネチャからは分からないが、ブールの値が真のときは、入力したモナドを実行する。そうでないときは、何もしないでreturn()を返すのがwhenの約束である。
whenを用いた例を簡単なプログラムで示す。名前がキーボードから入力され、Johnであれば出力するのだが、それ以外の名前が来たときは出力しないことにする。このプログラムは次のようになる。
import Control.Monad main = do x <- getLine when (x == "John") $ do putStrLn x
この処理を永遠に続けるためには、foreverという関数を用いて次のようにする。
import Control.Monad main = forever $ do x <- getLine when (x == "John") $ do putStrLn x
次にforMに移る。forMの型シグネチャは次のようになっている。
forM :: Monad m => [a] -> (a -> m b) -> m [b]
これからわかるように、forMはリストを受け取り、リストの要素を関数でモナドに変換し、そのリストを返すというものである。
forMを用いた簡単なプログラムを例に挙げる。1から10までの数字を入力とし、それぞれの2倍を出力するプログラムは次のようになる。
import Control.Monad main = do forM [1..10] $ \x -> do print $ x * 2
これを実行すると次のようになる。
*Main Control.Monad> main 2 4 6 8 10 12 14 16 18 20 [(),(),(),(),(),(),(),(),(),()]
また、HaskellにはモナドのためのmapMもある。これは、forMの引数をフリップしたものと同じである。先のプログラムをmapMで記述すると次のようになる。
mapM (\x -> print $ x * 2) [1..10]
また、m [b]の出力がわずらわしいので、これを出力しないようにするためには、mapMをmapM_で置き換えてればよい。
mapM_ (\x -> print $ x * 2) [1..10]
結果は次のようになる。
*Main Control.Monad> main 2 4 6 8 10 12 14 16 18 20
4.問題
問題1:二つの文字列を入力しそれをつなげるプログラムを(>>=)を用いて作成しなさい。
問題2:上記のプログラムをdo記法で書き表しなさい。
問題3:二つの数を入力しその積を得るプログラムを(>>=)を用いて作成しなさい。
問題4:上記のプログラムをdo記法で書き表しなさい。
問題5:二つのブール値を入力しその論理和を得るプログラムを(>>=)を用いて作成しなさい。
問題6:上記のプログラムをdo記法で書き表しなさい。
問題7:三つの数を入力しその和を得るプログラムを(>>=)を用いて作成しなさい。
問題8:上記のプログラムをdo記法で書き表しなさい。
問題9:whenを用いて、数を連続して入力し、その都度、整数であれば出力し、そうでなければ何もしないプログラムを作成しなさい。
問題10:10001から10010までの数を入力し、それぞれのかすの3倍を出力するプログラムをforMを用いて作成しなさい。また、このプログラムをmapM_で書き直しなさい。