今回は GHC 7.10 へ移行した場合にモナドのインスタンス化がどうなるのかについて簡単に書いておこうと思う。
GHC 7.8 のマニュアルでMonadの定義を見てみると、
class Monad m where
・・・
となっており、Monad にするために最低必要なものは、(>>=)
と return
の定義、そして「モナド(3)」で説明した「モナド則」を満たしていることの確認、以上を行えばモナドになることができた。ところが、度々触れたように、GHC 7.10 からは Applicative を継承していなければモナドになれなくなってしまった。GHC 7.10.1 はまだリリースされていないが、RC21を使うことができるのでいくつか確認してみた。
GHC 7.10.1 のマニュアルを見てみると、
class Applicative m => Monad (m :: * -> *) where
・・・
class Functor f => Applicative (f :: * -> *) where
・・・
となっている。つまり、モナドにするためには Applicative に加えて Functor にもする必要がある。Functor, Applicative にするための要件は以下のようなものである:
Functor
関数 fmap :: (a -> b) -> f a -> f b
を定義し、以下のファンクター則を満たす:
fmap id == id
fmap (f . g) == fmap f . fmap g
Applicative
関数 pure :: a -> f a
と (<*>) :: f (a -> b) -> f a -> f b
を定義し、以下のアプリカティブ則を満たす:
pure id <*> v = v
pure (.) <*> u <*> v <*> w == u <*> (v <*> w)
pure f <*> pure x == pure (f x)
u <*> pure y == pure ($ y) <*> u
モナド以外に3つの関数を定義して6個の条件を満たすことを確認しなければならないわけであるが、実は fmap
や pure
, (<*>)
と同じ関数がすでにモナドモジュールで定義されているので、とりあえずデフォルトの実装で良いのであれば、機械的に以下のように書けば Functor/Applicative の条件を満たすことが可能である。
import Control.Monad(liftM,ap)
instance Monad T where
return x = ... -- モナド則を満たすように return, (>>=) を
x >>= f = ... -- 定義する。ここは自力でがんばる。
instance Applicative T where
pure = return
(<*>) = ap
instance Functor T where
fmap = liftM
これは、“Functor-Applicative-Monad Proposal”という文書の 2.1 Missing superclasses から拝借している。引用元では Applicative の import もしているが、7.10.1 では Applicative の定義が Prelude に入っているので、import しなくても大丈夫である。
余談。
上記引用元に superclass という用語がある。今の例で言えば、Functor が Applicative の、Applicative が Monad の superclass である。 superclass の対語は subclass であろう2。日本語に直訳すると上位クラスと下位クラスといったところだろうか。しかし、この用語は良くないと思っている。何をもって「上位」とか「スーパー」とか言うのかが分かりにくいからである。“super-”という接頭辞は「大きい」とか「強い」とかの意味を持ち、集合の superset は(他方を包含する)大きい集合を表してる。その意味においては Applicative は Monad よりも多くのものを対象としているので super なのだが、一方、Applicative よりも Monad の方が「強い」3という表現が使われることもあり、この意味では Monad のほうが super である。super ではなく base、つまり「基底クラス」とし、「サブクラス」の代わりに「派生クラス」を用いた方が紛らわしく無いと思う。
上の例では liftM を使って Functor のインスタンスにしていたが、GHCの言語拡張を使えばコンパイラがやってくれる。
{-# LANGUAGE DeriveFunctor #-} -- ファイルの冒頭に書いておく
data T = (データ型の定義) deriving Functor
こう書いておけば、instance Functor T ...
の宣言は不要となる。「もしかして7.10では Applicative も…」とちょっと期待したが、DeriveApplicative
は無いようである。
7.10 に移行したら Monad のインスタンス化がとても大変になるのだろうか…と思ったりもしたのだが、労力としては従来とそう違わずにできるのでホッとしている。