モナドの正体が分かると、
次はモナドを実装してみたくなるものです。
という訳で試しに Python でモナドを実装してみましょう。
別にどんな言語でも構わないのですが、
クロージャや部分適用が簡単に使えない言語では本質的でないところで苦労する羽目になるので、
今回は Python を使うことにしました。
なお、今回作成した Python によるモナドの実装は GitHub で公開中です。
モナドの具体例で一番簡単なものは Maybe
でしょう。
まずは Maybe
を Python のクラスとして実装して、
そこから一般的なところを括りだして Monad
クラスを作ることにしましょう。
モナドの構成要素は以下の3つです:
m
m
にラップするための関数 return
>>=
これらを順に実装していきましょう。
m
型を作るだけなら簡単ですね。
class Maybe:
pass
return
return
は Python では予約語ですので、実装にあたっては名前を変える必要があります。
それに普通の関数として実装するには色々とやり辛いので、return
は m
に相当するクラス Maybe
のコンストラクターとして実装することにしましょう。
def __init__(self, a):
self.value = a
return
>>=
Haskell では >>=
のようにオレオレ演算子を自由に定義できますが、
大多数の言語ではそのような芸当はできませんので、
ここでは m
に相当するクラス Maybe
のメソッドとして定義することにしましょう。
def bind(self, a_to_m_b):
if self is Nothing:
return Nothing
else:
return a_to_m_b(self.value)
ただ、 bind
メソッドだけでは a.bind(f).bind(g).bind(...)
のようなコードになってしまい、
何となく美しくない感じがするので、
既存の演算子 |
をオーバーロードして >>=
の代わりに利用できるようにしておきたいと思います
(本来 |
は Python ではビットOR演算子です。>>=
の代わりにしたのはシェルにおけるパイプからの連想です。
シェルも見様によってはモナドですしね)。
こうすれば a | f | g | ...
というコードが書けて気分が軽やかになりますね。
def __or__(self, a_to_m_b):
return self.bind(a_to_m_b)
Just
と Nothing
Haskell では
data Maybe a = Just a | Nothing
という1文で
Maybe
Maybe
型の値を作る関数 Just
Maybe
型の値(を作る関数) Nothing
を定義することができます(正確には関数ではなくて値構築子ですが、ここでは関数と見做して問題ありません)。Maybe
は実装しましたので、残りの Just
と Nothing
も実装しましょう。
Just
の実装はとても簡単です
(__repr__
は後で REPL を使った時の表示を見易くするために追加してます)。
class Just(Maybe):
def __repr__(self):
return 'Just(%r)' % self.value
Nothing
はちょっと特殊です。Just
で作成される値(とその表示結果)を区別したいため、Just
とは別のサブクラスを作っておき、
さらに Nothing
用のサブクラスは妙なことをしない限り他所から参照できないようにしておきます。
class Nothing(Maybe):
def __repr__(self):
return 'Nothing'
Nothing = Nothing(Nothing)
Maybe
のテスト簡単な例として、ゼロ除算で例外を出す代わりに Nothing
を返す safe_div
関数を作ってみましょう(ただ safe_div
では部分適用したときに引数の順序が問題になるため、実際には引数を逆にした safe_rdiv
を作ってそちらを使うことになります)。
>>> import functools
>>> curry = functools.partial
>>> def flip(f):
... return lambda x, y: f(y, x)
...
>>> def safe_div(dividend, divisor):
... if divisor == 0:
... return Nothing
... else:
... return Just(dividend / divisor)
...
>>> safe_rdiv = flip(safe_div)
Python 的には functools.partial
で部分適用ができます。
また、functools.partial
だと長ったらしいので別名 curry
を使うことにしています。
どのみち Haskell のように優雅な見た目にはならないのですが、
部分適用が簡単にできるだけ幸せな方だと思いましょう。
>>> Just(4) | curry(safe_rdiv, 2)
Just(2)
>>> Just(4) | curry(safe_rdiv, 2) | curry(safe_rdiv, 2)
Just(1)
>>> Just(4) | curry(safe_rdiv, 0)
Nothing
>>> Just(4) | curry(safe_rdiv, 0) | curry(safe_rdiv, 5)
Nothing
>>> # return a >>= f === f a
>>> `Just(8) | curry(safe_rdiv, 2)` == `curry(safe_rdiv, 2)(8)`
True
>>> # m >>= return === m
>>> `Just(3) | Just` == `Just(3)`
True
>>> # (m >>= f) >>= g === m >>= (\\x -> f x >>= g)
>>> f = curry(safe_rdiv, 2)
>>> g = curry(safe_rdiv, 3)
>>> `(Just(12) | f) | g` == `Just(12) | (lambda x: f(x) | g)`
True
Monad
への一般化Maybe
のようなモナドの具体例を次々に作っていったとき、
実装が異なってくるのは bind
の定義のみです。
という訳で bind
の中味はダミーにした Monad
クラスを作りましょう。
class Monad:
def __init__(self, a):
self.value = a
return
def bind(self, a_to_m_b):
raise NotImplementedError
def __or__(self, a_to_m_b):
return self.bind(a_to_m_b)
こうすれば Maybe
の実装は bind
だけオーバーライドすればよくなるので、
以下のように簡略化できます:
class Maybe(Monad):
def bind(self, a_to_m_b):
if self is Nothing:
return Nothing
else:
return a_to_m_b(self.value)
これでオレオレモナドが実装し放題です。やりましたね。
次回は Vim script 編です。