まず、IOモナドというのは、mainの戻り値として返さなければ、何の副作用も発生しない単なるデータにすぎないということを理解してください。
例えば、以下のようなコードを書いても、入力待ちになることなく1が表示されます。
haskell
1main = print $ length [getLine]
getLineが評価されていないからではないかと思う人もいるかもしれませんが、seqで明示的に評価しても、入力待ちになることはありません。
haskell
1main = print $ getLine `seq` length [getLine]
あとは、Haskellの処理系が、mainを以下のように処理していると考えれば、「Haskellの世界で」の評価は参照透過だとみなせます。
- 値がIOモナドによるコマンドだった場合は、「Haskellの外で」コマンドを実行する
- 値が
a >>= fという形だったら、まずaを「Haskellの世界で」評価し、それで返ってきたIOモナドによるコマンドを「Haskellの外で」実行し、その結果を引数に「Haskellの世界で」fを呼び出す - 値が
a >> bという形だったら、まずaを「Haskellの世界で」評価し、それで返ってきたIOモナドによるコマンドを「Haskellの外で」実行し、その結果を無視してbを「Haskellの世界で」評価する
「Haskellの世界で」コマンドの生成だけを行って、そのコマンドの実行を「Haskellの外で」行うことで、副作用がHaskellの世界に入り込むことを防いでいるのです。
質問のコードは、doを使わずに書くと以下のようになります。
haskell
1main = getLine >>= (\str1 -> getLine >>= (\str2 -> print str1 >> print str2))
これを先ほどの処理に通すと、以下の動作になります。
- まず、
>>=の前のgetLineを「Haskellの世界で」評価し、「一行読み込むというコマンド」が返る - 「一行読み込むというコマンド」を「Haskellの外で」実行する
- その結果を x とすると、
(\str1 -> getLine >>= (\str2 -> print str1 >> print str2)) xが「Haskellの世界で」評価され、getLine >>= (\str2 -> print x >> print str2)が返る - また
>>=が現れたので、その前のgetLineを「Haskellの世界で」評価し、「一行読み込むというコマンド」が返る - 「一行読み込むというコマンド」を「Haskellの外で」実行する
- その結果を y とすると、
(\str2 -> print x >> print str2) yが「Haskellの世界で」評価され、print x >> print yが返る >>が現れたので、その前のprint xを「Haskellの世界で」評価し、「x を出力するというコマンド」が返る- 「x を出力するというコマンド」を「Haskellの外で」実行する
- その結果を無視して、
print yを「Haskellの世界で」評価し、「y を出力するというコマンド」が返る - 「y を出力するというコマンド」を「Haskellの外で」実行する
上記の 1.と4.でgetLineを評価した結果が、どちらも「一行読み込むというコマンド」という同一の値である(=参照透過である)ことに注意してください。

0 コメント