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

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

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

V-1例えば 、Haskell で getchar, putchar に対応する関数は、それぞれ次のような型を持っている。

getChar ::putChar ::こ の 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 と置き換えられないことが明白である。

Q 5.1.1 C-code 1 と C-code 2 に対応する Haskell プログラム（の断片）を getChar,putChar, >>=を使って書け。

副作用を持つプログラムは実行する順番により意味が変わるので、並列に実行されるプログラムでは問題となることがある。参照透明性を保ちながらアクションを扱えることは理論的に有用であるだけでなく、並列コンピューティングなど実用面でも必要となってきている。

5.2 モナド とは
このような、さまざ まな言語で副作用として実現される特徴（ 入出力・破壊的代入・例外処理・非決定性など ）に対するアクションの型が 、実は共通の構造を持っていて、同じ構造を持つ演算子で取り扱えることがわかっている。この共通(monad) という。つまり、の構造を持つ型（正確には型構成子）のことをモナド はアクションの型である。上記の IO もモナド である。ただし 、モナド のV-2

中にはアクションと言う言葉がふさわしくないものもあるので、以下では代わりに “計算” (computation) という言葉を使う。
具体的にはモナド とはunitM :: a → M abindM :: M a → (a → M b) → M bという型の関数の存在する型構成子 M のことである。
（ bindM が上記の >>= 演算子に相当する。）より厳密には、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に渡して、続けて評価する計算を表す。

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

123

class Monad m wherereturn :: a -> m a(>>=) :: m a -> (a -> m b) ->-- 上の unitM に対応
m b -- 上の bindM に対応

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

5.4

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

V-3

12
34
5-- type IO a +-- instance Monad IO where-return a = \ w -> (a, w)-m >>= k = \ w -> let (a, w1) = m w in k a w1
ただし RealWorld は 、コンピューター全体のファイルなど の状態を表す型である。
IO は Haskell で入出力や状態を効率的に扱うために、処理系で特別な扱いを受ける。putChar, getChar の他にも、主なプリミティブとして以下のような関数がある。

12345putStr
putStrLn:: String -> IO ():: String -> IO ()getLine:: IO String
getContents :: IO String-- 文字列を出力する-- 文字列を出力して、改行する-- 一行を読み込む-- EOF まで読み込む

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

12345-- import Data.IORef が必要
newIORef
readIORef
writeIORef:: a -> IO (IORef a)-- 新しい参照の作成:: IORef a -> IO a-- 参照の読出し:: IORef a -> a -> IO () -- 参照への書込み

ところで、Haskell のプログラムを、GHCi のような対話環境ではなく、ghc コ
マンドで実行可能ファイルにコンパイルして実行するとき、C と同じように mainという名前の関数から実行が開始されることになっている。main 関数は、IO tという形の型（ t は任意の型）を持たなければいけないことになっている。
たとえば 、標準入力から読み込んだ文字列の大文字を小文字に変換したものと小文字を大文字に変換したものを出力するプログラムは以下のようになる。

12
3
4
5
6

import Data.Char-- Data.Char モジュールを import する-- toLower, toUpper :: Char -> Char は大文字・小文字の変換関数
main :: IO ()main = getContents >>= (\ s -> putStr (map toLower s)>>= (\ _ -> putStr (map toUpper s)))
ラムダ抽象（ \ . . . -> . . . ）は、どんな中置演算子よりも優先順位が低いので、上記の定義は括弧を省略し 、さらにレイアウトを整えて、次のように書くことが多い。

123

main = getContents>>= \ s ->putStr (map toLower s) >>= \ _ ->putStr (map toUpper s)
以降のプログラムでは、このように括弧を省略する。

V-4

実は、Haskell では Monad クラスに対して、do 記法という糖衣構文を用意していて、この関数は次のように書くこともできる。

123

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 行のみを出力する。
12
34main :: IO ()main = do all <- getContents
let ls = lines all
putStrLn (head ls)
ただし 、lines :: String -> [String] は文字列を改行文字で分割する関数である。
次に、先ほど 紹介した参照型 IORef を利用したプログラムの例をあげる。ただし 、これは無理矢理 IORef を使った例であり、IORef を使わずに同じ動作をするプログラムを記述することは可能である。

1 import Data.IORef
2
3 ioRefTest r = readIORef r >>= \ i ->4
if i<=0 then return ()5else putStrLn (show i) >>= \ _ ->6writeIORef r (i-1) >>= \ _ ->7ioRefTest r
8
9 main = newIORef 10 >>= \ r ->10ioRefTest r
説明を簡単にするため、以下のプログラム例ではモナドを型クラス Monad のインスタンスとして宣言していない。この宣言を追加して、do 記法を使うプログラムに書き直すことは暗黙の練習問題とする。
問 5.4.1 IO モナド を利用して次のようなプログラムを作成せよ。
（ 次節で紹介している関数を使用するかもしれない。）

V-5

1. 入力文字列を行毎に大文字と小文字に変換して出力する。
2. 入力文字列中の数字（ ’0’〜’9’ ）の出現回数をカウントして出力する。
3. 入力文字列中の’@’ が出現する最初の 10 行だけを出力する。

5.5

続・有用なリスト 処理関数

以前紹介した以外に文字列処理プログラムで有用と思われるリスト処理関数を、以下でいくつか紹介する。これらの関数は Prelude（標準ライブラリ）に定義済みである（ sort を除く）。

123456789101112131415161718192021222324252627282930313233343536373839-- lines は文字列を改行文字のところで分割して、文字列のリストにする。
-- 結果の文字列には改行文字は含まれない。
lines:: String-> [String]-- words は文字列を空白文字で分割して、文字列のリストにする。
words:: String-> [String]-- unlines は lines の逆操作である。
-- 各文字列の末尾に改行文字を追加し 、一つに結合する。
unlines:: [String] -> String-- unwords は words の逆操作である。
-- 各文字列を空白で区切って結合する。
unwords:: [String] -> String-- take n xs は xs の長さ n の先頭部分を返す。
-- n > length xs のときは xs 自身を返す。
take:: Int -> [a] -> [a]-- drop n xs は xs の最初の n 個の要素を除いた末尾部分を返す。
-- n > length xs のときは [] を返す
drop:: Int -> [a] -> [a]-- takeWhile は述語 p とリスト xs を受け取り, p を満たす要素-- だけからなる xs の最も長い先頭部分（ 空の場合もある）を返す。
--- > takeWhile (< 3) [1,2,3,4,1,2,3,4] == [1,2]-- > takeWhile (< 9) [1,2,3] == [1,2,3]-- > takeWhile (< 0) [1,2,3] == []takeWhile:: (a -> Bool) -> [a] -> [a]-- dropWhile p xs は、takeWhile p xs の残りの末尾部分を返す。
--- > dropWhile (< 3) [1,2,3,4,5,1,2,3] == [3,4,5,1,2,3]-- > dropWhile (< 9) [1,2,3] == []-- > dropWhile (< 0) [1,2,3] == [1,2,3]
dropWhile:: (a -> Bool) -> [a] -> [a]-- any は述語とリストを受け取り、リストのなかのどれかの要素が述語
V-6

404142434445464748
49
50
5152
53
5455
56
57
58
59
60
616263-- を満たすかど うか判定する。
any:: (a -> Bool) -> [a] -> Bool-- all は述語とリストを受け取り、リストのなかのすべての要素が述語-- を満たすかど うか判定する。
all:: (a -> Bool) -> [a] -> Bool-- リスト（ 空ではいけない）の先頭要素を取り出す。
head:: [a] -> a-- （ 非空かつ有限な）リストの最後の要素を取り出す。
last:: [a] -> a-- リスト（ 空ではいけない）の先頭要素を除いた末尾部分を取り出す。
tail:: [a] -> [a]-- リストの要素として含まれているか否かを判定する。
-- 通常 x ‘elem‘ xs のように中置記法で用いる。
elem:: (Eq a) => a -> [a] -> Bool-- import Data.List が必要-- sort 関数は安定なソートアルゴ リズムの実装である。
-- 比較関数を引数にとる sortBy 関数もある
sort:: (Ord a) => [a] -> [a]Q 5.5.1 次の式の値は何か ?
1. drop 2 [1,4,5]
2. takeWhile (<0) [-1, -3, 2, -2, 7]
3. any (>0) [-1, -3, -5]
4. 3 ‘elem‘ [2, 5, 3, 1]

5.6 さらに詳し く知りたい人のために . . .
文献 [1] は、モナド とリストの内包表記の関係について解説している。

この章の参考文献[1] Philip Wadler 「 Comprehending Monads 」
ACM Conference on Lisp and Functional Programming, Nice (France), 1990 年 6月

V-7