IOモナドとdo記法

まずはじめに、HaskellでのHelloWorld。

module Main where

main :: IO ()
main = print "Hello,World!"

この"()"というのが気になったのですが、どうやらちゃんとPrelude(?)で定義されているようです。

Prelude> :i ()
data () = () 	-- Defined in GHC.Unit
instance Bounded () -- Defined in GHC.Enum
instance Enum () -- Defined in GHC.Enum
instance Eq () -- Defined in Data.Tuple
instance Ord () -- Defined in Data.Tuple
instance Read () -- Defined in GHC.Read
instance Show () -- Defined in GHC.Show

また、

Prelude> :i putStrLn
putStrLn :: String -> IO () 	-- Defined in System.IO
Prelude> :i print
print :: (Show a) => a -> IO () 	-- Defined in System.IO
Prelude> 

こんな感じで、出力を表すデータ型は基本的に「IO ()」を返すようになっている事から、これらの関数をmain関数内で「手続き的に」使うための「約束事」だと思えば良いみたいです。

手続き的と言えば、例えばJavaの次のようなサンプルをHaskellで実装する事を考えてみます。

import java.util.Scanner;

public class Sample {
	/**
	 * aとbを取得し"a is b"を返す
	 * @param a
	 * @param b
	 * @return
	 */
	private static String foo(String a,String b){
		return a+" is "+b;
	}
	/**
	 * 主処理
	 * @param args
	 */
	public static void main(String[] args){
		Scanner scan = new Scanner(System.in);
		System.out.println("Please input a and b");
		String a = scan.next();
		String b = scan.next();
		System.out.println(foo(a,b));
	}
}

これを関数型言語のアプローチで実装しようとすると厄介なのですが、IOモナドとlambdaを使えば次のようにして書けます。

Prelude> let foo a b = a ++ " is " ++ b
Prelude> putStrLn "Please input a and b" >> getLine >>= (\a -> getLine >>= (\b -> putStrLn (foo a b)))
Please input a and b
Haskell
Cool
Haskell is Cool

可読性は良くないですが、処理が左から右に流れていく感じが、手続き処理的に見えないことも無いですね。
そこで、do記法の出番となります。

Prelude> let foo a b = a ++ " is " ++ b
Prelude> do { putStrLn "Please input a and b" ; a <- getLine ; b <- getLine ; putStrLn (foo a b) }
Please input a and b
Haskell
Cool
Haskell is Cool

これは先ほどのモナドを使った例の糖衣構文だそうですが、ほとんど手続き言語に見え、それでいて(IOはモナドなので)純粋さは保たれています。Haskell勉強し始めた頃、この記法を見て違和感を感じて、ずっと遠ざけていたのですが、これでスッキリしました。

そして、do記法はインデントを使って複数行にまたがって書ける事、putStrLnの戻り値が「IO ()」な事から、次のようにmain関数を書くことができる、というワケですね。

module Main where

foo :: String -> String -> String
foo a b = a ++ " is " ++ b

main :: IO ()
main = do
    putStrLn "Please input a and b"
    a <- getLine
    b <- getLine
    putStrLn (foo a b)

んー、美しい!