マイ・モナドを作る

ユーザ定義の代数的データ型をモナドにしてみよう。といっても、大したものを作るのではなく、モナドの仕組みをみるために非常に単純なデータ型を作って操作してみるだけだ。

モナドにするためのデータ型 M を次のように定義する。

Prelude> data M a = M a deriving Show

Prelude> M "hello"
M "hello"
Prelude> M 1
M 1

上の例で分かるように、データコンストラクタ M は Haskell の値 a を M a 型の値に写像する関数のような働きをしている。圏論の用語を使うと、データコンストラクタ M は、Haskell の値 a を M a に写す a ---> M a の関手である。

このデータ型 M をモナドにするために、これをMonad クラスのインスタンスにする。

Prelude> :set +m
Prelude> instance Monad M where
Prelude|   (M x) >>= f = f x
Prelude|   return x = M x
Prelude|

これだけの手続きでユーザ定義の M a 型のデータはモナドに変身する。つまり、M a 型をモナドにするには、M a 型に >>= ( bind 演算子) と return 関数を使えるようにするだけでよい。

つまり、モナドを使いこなすためには関手 M と >>= と return の間の関係を知っていればいいということだ。

Haskell のモナドは Kliesli 圏のデータを操作するためのルールなのだ。大雑把にいうと圏とは対象 (object) とそれを結ぶ射 (arrow) の集まりだ。

Kleisli 圏の場合は対象は Haskell の値 a (1 でも "hello" でも [1,2,3] でもなんでも良い)と関手であるデータコンストラクタ M によって写像された a の像 M a からなっている。

対象を結ぶ射 f :: a -> M b はそれらを結ぶ関数 f だ。M b になっているのは戻値のMのコンテナの値 b の型は引数 a の型と異なっていてもいいからだ。f はその型の signature でわかるように、引数が a で値(戻値) が M b 型になっている。このタイプの関数(射)を Kleisli 射という。例えば次のような関数 f は Kleisli 射だ。

f :: Int -> M [Int]
f x = M [x * 2]

Kleisli 圏はこのような対象 a と M b と Kleisli 射で構成されている。

a ---( Kleisli 射 )---> M b

ここで、Kleisli 圏について、Kleisli 射の合成を考えてみる。「合成」という言葉からます思いつくのは「写像の合成」だ。写像の合成と言うと g . f のような合成写像をイメージするだろう。この場合、

a ---( f )---> b ---( g )---> c

のように写像 f による a の像 b にさらに g を関数適用させることによって合成写像 g . f による a の像 c が得られる。

しかし、Kleisli 射の場合の射の合成 >>= は事情が少し異なる。Klisli 射 m と n の合成の様子は次のようになる。

a ---( m )---> M b ---> b ---( n ) ---> M c

a に Kleisli 射 m を関数適用させた値 M b はそのまま射 n に利用されるのではなく、値 M b のコンテナから値 b を一旦取り出して、それに射 n を関数適用することになる。上の模式図は Haskell で記述すると次のようになる。

M a >>= m >>= n -----> M c

ここで m >>= n = k となるような関数を考えると、k は a ---( k )---> M c となるような射(関数)になる。つまり射 k は a ---( m )---> Mb という射と a ---( n )---> M c という射の合成射になる。しかし、これは関数の合成とは違う。合成された射 k も m や n と同じ型の射(関数)になるからだ。つまり、

m :: a -> M b
n :: a -> M b
k :: a -> M b

のように3つの射(関数)は同じ型の射(関数)だ。したがってそのような射(関数)の集合を考えると、m >>= n = k のような式が成り立つので、>>= 演算子はそれらの射の2項演算と考えることができる。これは、Kleisli 射の集合を、0, 1, 2, ... の自然数と同じ構造として考えることができることを示している。

このことが、Kleisli 射について重要な性質をもたらす。つまり、Kleisli 射をどれだけ >>= 演算子で合成しても、その結果はやはり Kleisli 射になるからだ。Kleisli 射をどのように >>= 演算子でつないでもその結果が Kleisli 射になることが保証されているので、プログラマは、要素的な Kleisli 射を作っておけばそれらの部品を組み合わせて、自由にプログラムを発展させる事ができる。

さらに、Kleisli 射の世界は Kleisli 射で完結しているので、Haskell の他の部分から Kleisli 射に関連する機能を隔離することができる。他のプログラムに影響することがないし、他のプログラムからの影響も極力少なくすることができる。

これらの2点において、モナドを利用したプログラムを作れば、プログラマのモジュール化されたプログラムを作る労作を極力減らしてくれる。

また、Monad はタイプクラスでもあるので、 >>= 演算子と return 関数は多相関数である。ということは、様々なモナドに対して同じような操作性を提供するということだ。つまり、様々なモナドに対し統一されたインターフェースを提供することになる。

理論的な話が長くなったが、上で作成した自前のモナドについて実験してみよう。

まず、モナド M に >>= が適用できるかどうか見てみる。

Prelude> M 2 >>= \x -> M (x * 2)
M 4

モナド M の値 M 2 のコンテナの値 2 が >>= 演算子によって取り出され、a -> M b 型の無名 Kleisli 射(関数)の引数として渡される。そうして、それは Kliesli 射によって2倍され、さらにデータコンストラクタ M に包まれて M 4 が返される。

return の働きも次のように調べることができる。return は多相関数なので、Haskell の型推定機能を利用するためには、その値が M a 型であることを指定するために :: 演算子による型の指定が必要になる。

Prelude> ((return 2) :: M Int) >>= \x -> M (x * 2)
M 4

それでは、Kleisli 射の合成について見てみよう。double と add_one という2つの Kleisli 射を定義する。

Prelude> let
Prelude| double x = M (x * 2)
Prelude| add_one x = M (x + 1)
Prelude|

そうして M 2 をこの2つの射の合成射 double >>= add_one に与えてみる。

Prelude> M 2 >>= double >>= add_one
M 5

また、double と add_one の順序を入れ替えてみる。

Prelude> M 2 >>= add_one >>= double
M 6

このように一度要素的な射 double と addone を作成しておけば、その部品を組み合わせて様々な用途に適応させることができる。プログラムのモジュール化の効果だ。

また、M はモナドなので do 記法を使うことができる。

Prelude> do
Prelude|   x <- return 2
Prelude|   y <- double x
Prelude|   z <- add_one y
Prelude|   return z
Prelude|
M 5

さらに、外部からのデータをモナドに与えたいときは次のようにする。

Prelude> (\x -> return x >>= double >>= add_one) 2
M 5

このように、ユーザ定義のデータ型をモナドにするには、Monad クラスのインスタンスとして宣言し、>>= 演算子と return 関数の実装をするだけだ。

ユーザ定義のモナドを作ることによるモジュール化されたプログラムはそれほど多くの事ができるわけではないが、モジュール化されたプログラムを作るためのプログラマの負担を大きく軽減してくれる。また、IO モナドで格闘した後ならわかるが、モナドの表現力はそのシンプルすぎるほどの枠組みにもかかわらず思いのほか豊かなのだ。

IOモナドは Haskell 学習者の嫌われ者だが、しっかり取り組んだ後の頼もしさを知った後は、モナドの知識は手放せないものになる。
[PR]
by tnomura9 | 2013-05-01 19:46 | Haskell | Comments(0)
<< StateT モナドの使い方 Girls >>