Haskell : モナド

当分モナドには触れないつもりだったのですが、ちょっと思いついたので、一つ書いて見ます。
もちろん、副作用がない参照透過なHaskellにとっては全く重要ではないことですが
IOを行うモナドHaskellの自作してみようというネタです。unsafePerformIOという関数を利用していますが、低レベルで副作用を持った外部関数を呼んでいるイメージで。

module MyIO where

import Monad
import System.IO.Unsafe

print2 a = (unsafePerformIO (putStrLn a)) `seq` (A ())

data A a = A a
get (A a) = a

instance  Monad A  where
    --; 1.  a >>= f = f (get a)
    --; 2.  a >>= f = a `seq` (f (get a))
    --; 3.  a >>= f = let b = f (get a) in b `seq` a `seq` b
    return a = A a

start :: ()
start = let A a = k in a
  where
    k = do return ()
           print2 "Hello "
           print2 "World!"

途中、コメントアウトしている3種類のbindの実装があるのですが、ここの実装の違いがどういう振る舞いの違いを見せるかというところに注目。

1. a >>= f = f (get a)

MyIO> start
World!
()

print2関数で引数を使っていないので、bindの引数のaが捨てられてしまって、最後の"World!"のところしか出力されていません。

2. a >>= f = a `seq` (f (get a))

MyIO> start
Hello 
World!
()

そこで、bindの中で強制的に引数のaを評価してやると、正常に出力されるようになりました。

3. a >>= f = let b = f (get a) in b `seq` a `seq` b

MyIO> start
World!
Hello 
()

「a `seq` (f (get a))」の順序を逆にしてやれば、逆順に出力されるbindが作れるのですが、普通に作ると型が合わなくなるので、「b `seq` a `seq` b」のように回りくどいことをやっています。