モナドを実装する(Python編)


2011年 05月 11日

発端

モナドの正体が分かると、
次はモナドを実装してみたくなるものです。

という訳で試しに Python でモナドを実装してみましょう。
別にどんな言語でも構わないのですが、
クロージャや部分適用が簡単に使えない言語では本質的でないところで苦労する羽目になるので、
今回は Python を使うことにしました。

なお、今回作成した Python によるモナドの実装は GitHub で公開中です。

方針

モナドの具体例で一番簡単なものは Maybe でしょう。
まずは Maybe を Python のクラスとして実装して、
そこから一般的なところを括りだして Monad クラスを作ることにしましょう。

モナドの構成要素は以下の3つです:

  • 普通の値をモナドにラップするための型 m
  • 普通の値を m にラップするための関数 return
  • モナドでラップされた普通の値を取り出して処理を行う演算子 >>=

これらを順に実装していきましょう。

m

型を作るだけなら簡単ですね。

class Maybe:
  pass

return

return は Python では予約語ですので、実装にあたっては名前を変える必要があります。
それに普通の関数として実装するには色々とやり辛いので、
returnm に相当するクラス 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)

JustNothing

Haskell では

data Maybe a = Just a | Nothing

という1文で

  • 新しい型 Maybe
  • 任意の値を元に Maybe 型の値を作る関数 Just
  • 値が存在しないことを表す Maybe 型の値(を作る関数) Nothing

を定義することができます(正確には関数ではなくて値構築子ですが、ここでは関数と見做して問題ありません)。
Maybe は実装しましたので、残りの JustNothing も実装しましょう。

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 編です。