第 5 章 モナド
モナド（ monad ）は、Haskell（あるいは他の関数型言語）で破壊的代入（ 変数の値など 状態を変更すること ）や入出力のような、他の言語では “副作用” （ side effect ）として実現される特徴を扱うための手法である。
もともとは数学のカテゴ リ理論（ 圏論）で使われている言葉を借用したものであるが 、Haskell で使用するときには、背景となるカテゴ リ理論のことを知っている必要はない。

5.1

参照透明性と副作用

純粋な関数型言語には 、式は値を表すためだけのものであり、「 変数の出現はその定義の右辺の式で置き換えても全体の意味は変わらない」という性質がある。このような性質を（ referential transparency ）と呼び 、プログラムの “意味” を考察していく上でひじょうに重要な性質である。
（ 参照透明性のおかげで、帰納法などによる証明が容易になる。） 一方、副作用は、である。Haskell の式は副作用を持っていない。
C や ML のような言語では、入出力や破壊的代入を扱う部分では参照透明性は成り立たない。例えば 、C で、

c = getchar();putchar(c);putchar(c);とputchar(getchar()); putchar(getchar());とは、getchar() が副作用を持っているために異なるプログラムである。
（ 上のプログラムでは 1 文字、下のプログラムでは 2 文字の入力が消費される。）
一見、参照透明性と入出力や破壊的代入は相容れない性質のように見える。しかし 、Haskell では次のように考える。
入出力や、変数などの状態の変更は、何らかの “アクション ” であり、単なる値とは異なる型を持っている。副作用を持つ C 言語などの関数に対応する Haskell の関数は、このアクションを返す関数として考える。
（つまり “副” 作用ではなく、一人前の値として扱う。）
この “アクション ” の型は、アクションに対応している文脈でしか使用することができない。従って、アクションと値は区別され 、参照透明性が保たれる。
例えば 、Haskell で getchar, putchar に対応する関数は、それぞれ次のような型を持っている。

getChar ::putChar ::V-1

この IO という型構成子がアクションの型を表す。そもそも、C 言語の putchar(getchar ()) という式に対応する putChar getChar のような Haskell の式は、型エラーになってしまう。IO 型に用意されている演算子>>=を用いて、次のように書かなければいけない。

getChar >>= (\ c -> putChar c)
ここで、>>=の型は IO a -> (a -> IO b) -> IO b である。
（ 後述するように、実際にはより一般的な型を持っている。）この式では、getChar というアクションの結果得られる Char 型の値が、c という変数に束縛され 、続いて putChar c というアクションが実行される。アクションの型を持つ式から値だけを抽出するには、>>=のような演算子を介する必要がある。c = getChar と書けるわけではないので、文法の上からも型の面からも c が getChar と置き換えられないことが明白である。

5.2 モナド とは
このような、さまざまな言語で副作用として実現される特徴（入出力・破壊的代入・例外処理・非決定性など ）に対するアクションの型が、実は共通の構造を持っていて、同じ構造を持つ演算子で取り扱えることがわかっている。この共通の構造を持つ型（ 正確には型構成子）のことを(monad)という。つまり、モナドはアクションの型である。上記の IO もモナドである。ただし 、モナド の中にはアクションと言う言葉がふさわしくないものもあるので、以下では代わりに “計算” (computation)という言葉を使う。
具体的にはモナド とはunitM :: a → M abindM :: M a → (a → M b) → M bという型の関数の存在する型構成子 M のことである。より厳密には、unitM, bindM が(unitM a) ‘bindM‘ k = k am ‘bindM‘ (λa.unitM a) = m(m1 ‘bindM‘ k1 ) ‘bindM‘ k2 = m1 ‘bindM‘ (λa.(k1 a ‘bindM‘ k2 ))の 3 つの等式（ monad law ）を満たす必要がある。
直観的には M a という型は、を意味する。また、unitM と bindM の直観的な意味は次のとおりである。

• unitM a · · · 値 a をそのまま返すを表す。

• m ‘bindM‘ k · · · まず、m :: M a を評価し 、その結果の値を関数 k :: a → M b に渡して、続けて評価する計算を表す。
V-2

5.3 モナド と型クラス
モナド M の定義は模倣したい副作用により異なるし 、付随する関数 unitM, bindM の定義ももちろん異なる。しかし 、unitM, bindM は M をパラメータとして、決まった型を持つので、型クラスを用いてアド ホック多相な関数として定義することができる。
class Monad m wherereturn :: a -> m a-- 上の unitM に対応(>>=) :: m a -> (a -> m b) -> m b -- 上の bindM に対応

Monad は型ではなく、型構成子に対する型クラスになっている。

5.4

IO モナドIO は Haskell の Prelude（ 最初から読み込まれているライブラリ）に入っているモナド である。型
クラス Monad のインスタンスである。その定義は一般のプログラマからは見えない組込みの型となっている。ただし 、直観的には次のような定義を持つ型だと考えることができる。

IO は Haskell で入出力や状態を効率的に扱うために 、処理系で特別な扱いを受ける。putChar,getChar の他にも、主なプリミティブとして以下のような関数がある。
putStr
putStrLn:: String -> IO ():: String -> IO ()getLine:: IO String
getContents :: IO String-- 文字列を出力する-- 文字列を出力して、改行する-- 一行を入力する-- EOF まで入力する

これらの他にファイルに対して入出力するための関数なども用意されている。
さらに、Data.IORef というモジュールを import することで、破壊的代入が可能な参照型（ IORef ）を扱う関数も用意される。

newIORef
readIORef
writeIORef:: a -> IO (IORef a)-- 新しい参照の作成:: IORef a -> IO a-- 参照の読出し:: IORef a -> a -> IO () -- 参照への書込み

Haskell のプログラムを、GHCi のような対話環境ではなく、実行可能ファイルにコンパイルして実行するとき、C と同じように main という名前の関数から実行が開始されることになっているが 、main 関数は、IO τ という形の型（ τ は任意の型）を持たなければいけないことになっている。
たとえば 、標準入力から読み込んだ文字列の大文字を小文字に変換したものと小文字を大文字に変換したものを出力するプログラムは以下のようになる。
import Data.Char-- Data.Char モジュールを import する-- toLower, toUpper :: Char -> Char は大文字・小文字の変換関数
main :: IO ()main = getContents >>= (\ s -> putStr (map toLower s)>>= (\ _ -> putStr (map toUpper s)))V-3

ラムダ抽象（ \ . . . -> . . . ）は、どんな中置演算子よりも優先順位が低いので、上記の定義は括弧を省略し 、さらにレ イアウトを整えて、次のように書くことが多い。
main = getContents>>= \ s ->putStr (map toLower s) >>= \ _ ->putStr (map toUpper s)
以降のプログラムでは、このように括弧を省略する。
実は、Haskell では Monad クラスに対して、という糖衣構文を用意していて、この関数は次のように書くこともできる。
main = do { s <- getContents;
putStr (map toLower s);
putStr (map toUpper s) }（ do 式もレ イアウトルールの対象であり、適切にレ イアウトすればブレースやセミコロンを省略することができる。）
do 式は次のルールで翻訳される。

do {e} ⇒ e
do {e; stmts} ⇒ e >>= \ _-> do {stmts}do {x <- e; stmts} ⇒ e >>= \ x -> do {stmts}
do {let decls; stmts} ⇒ let decls in do {stmts}
getContents は遅延評価でファイルの内容を読み込む。つまり次のようなプログラムでは、ファイルのすべてを読み込むことはなく、最初の 1 行のみを出力する。
main :: IO ()main = do all <- getContents
let ls = lines all
putStrLn (head ls)
ただし 、lines :: String -> [String] は文字列を改行文字で分割する関数である。
説明を簡単にするため、以下のプログラム例ではモナドを型クラス Monad のインスタンスとして宣言していない。この宣言を追加して、do 記法を使うプログラムに書き直すことは暗黙の練習問題とする。
問 5.4.1 IO モナド を利用して次のようなプログラムを作成せよ。

• ファイル中の数字（ ’0’〜’9’ ）の出現回数をカウントする。
• ’@’ が 10 回目に出現する行までを出力する。
ヒント : lines, words といったライブラリ関数を調べて使用せよ。

この章の参考文献[1] Philip Wadler 「 Comprehending Monads 」
ACM Conference on Lisp and Functional Programming, Nice (France), 1990 年 6 月
モナド とリストの内包表記の関係について解説している。

V-4