do は >>= の構文糖衣です。do構文の中では、<- という記号が使えるので、あたかも代入しているような形でプログラムを書いてゆくことができます。
Haskellプログラミングに少し慣れてくると、do構文を使わなくても書けるようになり、そのほうが簡潔でわかりやすいと思えることもあります(主観)。
do構文で書かれたプログラムをdoを使わない形に書き換える手順をまとめてみました。
この記事で使う、書き換えるための道具一式です。==>の左は右に置き換えられるという意味です。
-- | ----------------------------------------------------
-- | (1) do { m1 } ==> m1
-- | (2) do { m1; m2 } ==> m1 >> do { m2 }
-- | (3) do { let s1; m1 s1 } ==> let s1 in do { m1 s1 }
-- | (3’) do { let s1; m1 s1 } ==> do { m1 s1 } where s1
-- | (4) do { x <- m1; m2 x } ==> m1 >>= (\x -> do { m2 x } )
-- | (5) do { x <- m1; let s = f x; m2 s } ==> do { s <- f <$> m1; m2 s }
-- | (6) do { x <- m1; y <- m2; let s = f x y; m3 s } ==> do { s <- f <$> m1 <*> m2; m3 s }
-- | (7) let s1; let s2 ==> let (s1; s2)
-- | (8) (\x -> f x) ==> f
-- | ----------------------------------------------------
これらを組み合わせて、段階的にdoを使わない形に変形してゆきます。
#do { m1 }
m1はMonadで、例えばIO ()です。これ1個がdo構文に入っている場合は、そのままdo構文からはずすことができます。
main :: IO () -- 以後すべてmainはIO ()です。型宣言を省略します。
main = do putStrLn "hello"
-- | (1) do { m1 } ==> m1
main = putStrLn "hello"
#do { m1; m2 }
m1もm2も、IO ()です。この場合はMonadの >> を使います。以下のように(2)と(1)を使って2段階でdoをなくせます。
main = do putStr "hello"
putStrLn " world"
-- | (2) do { m1; m2 } ==> m1 >> do { m2 }
main = putStr "hello"
>> do putStrLn " world"
-- | (1) do { m1 } ==> m1
main :: IO ()
main = putStr "hello"
>> putStrLn " world"
(>>) :: Monad m => m a -> m b -> m b
#do { x <-m1; m2 x }
m1の中に入っているものにxと名前をつけて、それをm2が使う場合です。
main = do x <- getLine
putStrLn x
-- | (4) do { x <- m1; m2 x } ==> m1 >>= (\x -> do { m2 x } )
main = getLine >>= (\x -> do { putStrLn x })
-- | (1) do { m1 } ==> m1
main = getLine >>= (\x -> putStrLn x)
-- | (8) (\x -> f x) ==> f
main = getLine >>= putStrLn
(>>=) :: Monad m => m a -> (a -> m b) -> m b
#do { let s1; m1 s1 }
s1はMonadではありません。それをm1が使う場合です。let~inを使う方法と、whereを使う方法があります。
main = do let s1 = "hello"
putStrLn s1
-- | (3) do { let s1; m1 } ==> let s1 in do { m1 s1 }
main = let s1 = "hello" in putStrLn s1
-- | (3') do { let s1; m1 } ==> let s1 in do { m1 s1 }
main = do { putStrLn s1 }
where s1 = "hello"
-- | (1) do { m1 } ==> m1
main = putStrLn s1
where s1 = "hello"
#do { let s1; let s2; m1 s1; m2 s2 }
letが2つあって、その結果を使っているm1とm2が2つ続いている場合
main = do let s1 = "hello"
let s2 = s1 ++ " world"
putStrLn s1
putStrLn s2
-- | (7) let s1; let s2 ==> let s1; s2
main = do let s1 = "hello"; s2 = s1 ++ " world"
putStrLn s1
putStrLn s2
-- | (3) do { let s1; m1 } ==> let s1 in do { m1 }
main = let s1 = "hello"; s2 = s1 ++ " world"
in do putStrLn s1
putStrLn s2
-- | (2) do { m1; m2 } ==> m1 >> do { m2 }
-- | (1) do { m1 } ==> m1
main = let s1 = "hello"; s2 = s1 ++ " world"
in putStrLn s1
>> putStrLn s2
-- | または
-- | (3') do { let s1; m1 } ==> do { m1 } where s1
main = do putStrLn s1
putStrLn s2
where s1 = "hello"; s2 = s1 ++ " world"
-- | (2) do { m1; m2 } ==> m1 >> do { m2 }
-- | (1) do { m1 } ==> m1
main = putStrLn s1
>> putStrLn s2
where s1 = "hello"
s2 = s1 ++ " world"
#do { x <- m1; let s = f x; m2 s }
fは普通の関数です。普通の関数をMonadの中にある値に適用する場合はファンクタの<$>が使えます。
main = do xs <- getLine
let n = sum . map (read :: String->Int) $ words xs
print n
-- | (5) do { x <- m1; let s = f x; m2 s } ==> do { s <- f <$> m1; m2 s }
main = do n <- (sum . map (read :: String->Int) . words) <$> getLine
print n
-- | (4) do { x <- m1; m2 } ==> m1 >>= (\x -> do { m2 } )
main = (sum . map (read :: String->Int) . words) <$> getLine >>= print
(<$>) :: Functor f => (a -> b) -> f a -> f b
(>>=) :: Monad m => m a -> (a -> m b) -> m b
#do { x <- m1; y <- m2; let s = f x y; m3 s }
Monadの中にある値2つを引数とする普通の関数の場合は、ファンクタの<$>とアプリカティブの<*>を使うと変換できます。
main = do s1 <- getLine
s2 <- getLine
let s3 = (++) s1 s2
putStrLn s3
-- | (6) do { x <- m1; y <- m2; let s = f x y; m3 s } ==> do { s <- f <$> m1 <*> m2; m3 s }
main = do s3 <- ((++) <$> getLine) <*> getLine
putStrLn s3
-- | (4) do { x <- m1; m2 } ==> m1 >>= (\x -> do { m2 } )
-- | (1) do { m1 } ==> m1
-- | (6) (\x -> f x) ==> f
main = ((++) <$> getLine) <*> getLine >>= putStrLn
(<$>) :: Functor f => (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
(>>=) :: Monad m => m a -> (a -> m b) -> m b
#do { x <- m1; y <- m2; z <- m3; let s = f x y z; m4 s }
Monadの中にある値3つを引数とする普通の関数の場合。さきほどの<*>をもう1回繰り返します。
main = do s1 <- getLine
s2 <- getLine
s3 <- getLine
let s4 = s1 ++ s2 ++ s3
putStrLn s4
-- | (6) do { x <- m1; y <- m2; let s = f x y; m3 s } ==> do { s <- f <$> m1 <*> m2; m3 s }
main = do s4 <- (++) <$> (((++) <$> getLine) <*> getLine) <*> getLine
putStrLn s4
-- | (4) do { x <- m1; m2 } ==> m1 >>= (\x -> do { m2 } )
-- | (1) do { m1 } ==> m1
-- | (6) (\x -> f x) ==> f
main = (++) <$> (((++) <$> getLine) <*> getLine) <*> getLine >>= putStrLn
(<$>) :: Functor f => (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
(>>=) :: Monad m => m a -> (a -> m b) -> m b
次はちょっと難しい
#do { x <- m1; y <- m2; let s3 = f x y; m3 s3; let s4 = g x y; m4 s4}
入力側Monadの中にある値を繰り返し出力側Monadで使う場合は、ファンクタの<$>とアプリカティブの<*>を組み合わせるパターンに持ち込めません
main = do s1 <- getLine
s2 <- getLine
let s3 = s1 ++ s2
putStrLn s3
let s4 = s1 ++ "," ++ s2
putStrLn s4
-- | (4) do { x <- m1; m2 x } ==> m1 >>= (\x -> do { m2 x } )
main = getLine >>= (\s1 ->
getLine >>= (\s2 ->
let s3 = (s1 ++ s2) in putStrLn s3
>> let s4 = (s1 ++ "," ++ s2) in putStrLn s4
))
(>>) :: Monad m => m a -> m b -> m b
(>>=) :: Monad m => m a -> (a -> m b) -> m b
入力側Monadの中にある値を繰り返し使う場合は、doで書いたほうがわかりやすいようにも思えます。やりたいことに応じて適切なほうを選んで書けばいいのだと思います。
エキスパートの方は、このような変換を頭の中でやって、短く簡潔なプログラムをどんどん書けるようになっているのだと推測しています。私はまだまだ修養が必要です。