5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

関数型言語におけるMonadクラスはApplicativeを継承するべきなのか?MonadとApplicativeとの関係を再確認する

Last updated at Posted at 2021-11-15

#はじめに

Haskellなどの関数型言語におけるMonadクラスの定義において、Applicativeクラスを継承する場合があります。しかしながら、その定義においてApplicativeの演算子$<*>$が直接使われることがなく、Applicativeクラスを継承することに対する有益性が少ないように思えます。
そこで本記事では、Monadクラスが本当にApplicativeクラスを継承する必要があるのか、MonadクラスとApplicativeクラスとの依存関係から考察していきます。

(数式のインライン表示で勝手に改行され、読みづらい点がありますが、ご了承願います。)

#MonadとApplicativeの定義

まず、MonadとApplicativeの公理系について見てみます。

##Monad

型変換子$m$がMonadであるとは、以下のMonad則と呼ばれる公理を満たす
$\operatorname{unit} : \forall A, A \to m,A$と
$\triangleright : \forall A,B, m,A \to (A \to m,B) \to m,B$が存在することである。

\forall A, \forall p : m\,A, p \triangleright \operatorname{unit} = p
\forall A\,B, \forall e : A,\forall p : A \to m\,B, \operatorname{unit} e \triangleright p = p\,e
\forall A\,B\,C, \forall p : m\,A, \forall q : A \to m\,B, \forall r : B \to m\,C,\\
(p \triangleright q) \triangleright r = p \triangleright (\lambda x. r\,x \triangleright r)

###同値な公理系

型変換子mがMonadであることはmがFunctor1かつ以下を満たす
$\eta : \forall A, A \to m,A$と
$\mu : \forall A, m,(m,A) \to m,A$が存在することと同値である。

\forall A\,B, \forall f : A \to B, \operatorname{fmap} f \circ \eta_A = \eta_B \circ f
\forall A\,B, \forall f : A \to B, \operatorname{fmap} f \circ \mu_A = \mu_B \circ \operatorname{fmap}\,(\operatorname{fmap} f)
\forall A, \mu_A \circ \eta_{(m\,A)} = \operatorname{id}_{(m\,A)}
\forall A, \mu_A \circ \operatorname{fmap} \eta_A = \operatorname{id}_{(m\,A)}
\forall A, \mu_A \circ \operatorname{fmap} \mu_A = \mu_A \circ \mu_{(m\,A)}

実際に、$m$がMonadのとき、
$\eta := \operatorname{unit}$、
$\mu ,x := x \triangleright \operatorname{id}$
と定義すれば、これらの規則を満たすことが分かり、
この公理系に対し、
$\operatorname{unit} := \eta$
$x \triangleright f := \mu,(\operatorname{fmap} f,x)$
と定義すると、Monadであることが分かります。

そこで本記事では、後者の定義である$\eta$と$\mu$を用いた記法でMonadを表現することにします。

##Applicative
型変換子$m$がApplicativeであるとは、以下のApplicative則と呼ばれる公理を満たす
$\eta : \forall A, A \to m,A$と
$<*> : \forall A,B, m,(A \to B) \to m,A \to m,B$が存在することである。

\forall A, \forall x : m\,A, (\eta_{(A \to A)} \operatorname{id}_A) <*> x = x
\forall A\,B\,C, \forall f : m\,(B \to C), \forall g : m\,(A \to B), \forall x : m\,A,\\
(((\eta_{((B \to C) \to (A \to B) \to A \to C)} (\lambda fg. f \circ g)) <*> f) <*> g) <*> x = f <*> (g <*> x)
\forall A\,B, \forall f : A \to B, \forall x : A, (\eta_{(A \to B)}\,f) <*> (\eta_A \,x) = \eta_B\,(f\,x)
\forall A\,B, \forall f:m\,(A \to B), \forall x : A,
f <*> (\eta_A\,x) = (\eta_{((A \to B) \to B)} (\lambda f.f\,x)) <*> f

2番目の公理の意味ですが、
$\operatorname{fmap3} : \forall A,B,C,D, (A \to B \to C \to D) \to m,A \to m,B \to m,C \to m,D$を

\operatorname{fmap3} f\,x\,y\,z := ((\mu_{(A \to B \to C \to D)} \,f) <*> x) <*> y) <*> z

と定義して書き直すと、ある種の結合則を表しているのが分かります。

\forall A\,B\,C, \forall f : m\,(B \to C), \forall g : m\,(A \to B), \forall x : m\,A,\\
\operatorname{fmap3}\,(\lambda fg. f \circ g)\,f\, g\,x = f <*> (g <*> x)

###ApplicativeのFunctor性

ApplicativeからFunctorを定義できます。
具体的には、Applicativeな型変換子$m$に対し、$\operatorname{fmap} : \forall A,B, (A \to B) \to m,A \to m,B$を

\operatorname{fmap} f\,x := (\eta\,f) <*> x

と定義してやればFunctor則を満たすことがわかります。

#MonadとApplicativeの関係

型変換子$m$がMonadであるとき、以下の2通りの方法でApplicativeを定義できます。

##Monadの2つのApplicative性

Monadである型変換子$m$に対し、

<*>^h, <*>^v : \forall A\,B, m\,(A \to B) \to m\,A \to m\,B

をそれぞれ以下に定義する。

f <*>^h x := \mu\,(\operatorname{fmap} (\lambda x. \operatorname{fmap} (\lambda f.f\,x)\,f)\,x)
f <*>^v x := \mu\,(\operatorname{fmap} (\lambda f.\operatorname{fmap} f\,x)\,f)

このとき、

(\eta,<*>^h)\\
(\eta,<*>^v)

はそれぞれApplicative則を満たす。


そこで本記事では便宜上、
$(\eta,<*>^h)$で定義されたものをh-Applicative、

$(\eta,<*>^v)$で定義されたものをv-Applicativeと呼ぶことにします。

h-Applicativeとv-Applicativeは、外側の$\operatorname{fmap}$に渡す引数の順番が異なります。
h-Applicativeでは、$\operatorname{fmap}$の第一引数内で$f$、第二引数で$x$を渡していますが、v-Applicativeではその逆です。

この違いによって、h-Applicativeとv-Applicativeはどちらも同じ型変換子の上のApplicativeではありますが、それぞれの$<*>$演算子は関数として異なる挙動をとります。

例えば、Listモナドの場合、

[\lambda n.n+1, \lambda n.n+2] <*>^h [10,20] = [11, 12, 21, 22]\\
[\lambda n.n+1, \lambda n.n+2] <*>^v [10,20] = [11, 21, 12, 22]

となり、一般には等号が成り立ちません。

すなわち、一般のMonadに対して、異なる2通りのApplicativeを実装することができます。2

#Monadクラスの定義において、Applicativeは継承するべきか?

上記のように、確かにMonadからApplicativeが定義できるので、Monadクラスを定義するときにApplicativeを継承してもいいように思えます。

実際にHaskellではApplicativeを継承してMonadが定義されています。

Control.Monad
class Applicative m => Monad m where
  (>>=) :: forall a b. m a -> (a -> m b) -> m b

  return :: a -> m a
  return = pure

しかしながら、Applicativeを継承するということは、Applicativeクラスを1つ固定し、その上にMonadを定義するということになります。

すなわち、Monadに対するApplicativeとして、h-Applicativeかv-Applicativeのどちらか、もしくはそれ以外に存在するかもしれないApplicativeクラスのうち1つが暗黙のうちに固定されているため、もう一つの実装のApplicative演算を使いたい場合に困ったり、これらの混同によるバグ発生の可能性が考えられます。

ならば、Monadクラスの定義自体はApplicativeの演算$<*>$に依存しないことから、Monadクラスの定義ではApplicativeを継承せず、Monad演算とApplicative演算を同時に使う関数だけその両方を仮定し、使い分けができるようにする方がいいのかもしれません。

そもそもの問題として、1つの型変換子に対して複数の異なる定義が存在する可能性があるApplicativeクラスをinstanceとして1つに固定してしまうことに原因があるようにも思えます。

ちなみにHaskellではこの問題に対し、以下の関数を定義することで一種の解決をしていると考えられます。

Control.Monad
ap :: Monad m => m (a -> b) -> m a -> m b
ap m1 m2 = do {x1 <- m1; x2 <- m2; return (x1 x2)}
-- Since many Applicative instances define (<*>) = ap, we
-- cannot define ap = (<*>)

Monad上の関数apの実装は本記事におけるv-Applicativeに一致するもので、v-Applicativeを使いたい場合はこちらの定義を用いることができます。
よって、HaskellにおいてはMonadを定義する上で継承するApplicativeクラスはh-Applicativeと等価となるようなものにすれば、これらを使い分けることができます。
Haskellではv-Applicativeと等しいApplicativeを継承する決まりになっているようです。
継承するApplicativeの実装を統一するという方法ならば、継承するクラスの実装が複数ある場合にも混同せずに使用できます。

コメントでのご指摘ありがとうございます。

#まとめ
関数型言語におけるMonadとApplicativeの依存関係について考察し、MonadからApplicative演算への定義は一般には異なる2通り存在することが分かりました。

この結果から、MonadでApplicativeの演算を使う場合は、2通りあるうちのどちらであるかを把握することが重要だと気づきました。

  1. Functorの公理は「関数型言語におけるTraversableとは何か?Functorと比較しながら解説する」に記載しています。

  2. IdモナドやMaybeモナドなど、個別のモナドに対してはh-Applicativeとv-Applicativeが一致する場合もあります。

5
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?