この記事は “モナドを理解するな” の続きものの記事です。
もし前記事を読んでいない方がいれば、まずそちらを読んでいただき、この記事が本当に必要かどうかお考えください。
TL; DR
モナドは単なる自己函手の圏におけるモノイド対象だよ。 なにか問題でも?
$$
\DeclareMathOperator{\bind}{\gg\!\!=}
\DeclareMathOperator{\composekleisli}{>\!\!\!=\!\!\!>}
$$
言い訳
この先の記述ではしばしば “箱モデルでいうと○○です” というような記述が頻出します。 前記事でさんざん “モナドは箱ではない (全ギレ)” としてきたのになんやねんお前と思うかもしれませんが、モナドは抽象的なものなので一つ具体例をおいておかないと説明が難しいです。 ですので、モナドそのものではなく、“モナドの具体例の一つ” として箱モデルを使用させていただきます。 モナドが箱なのではなく、箱がモナドなのです。 そして、ルールは一通りですが、操作は一通りではありません。 箱モデルにおける操作が他のモナドに適用できるとは限りませんのでご注意ください。
行くぞ、クライスリトリプルだ!! (謎の連携技風)
モナドとはルールです。
ここでいうルールとはいわゆるモナド則のことですが、まずはモナドを構成する “クライスリトリプル” のことを知っておくのがよいでしょう。 暗いアルパカ・スリのことではありません。
クライスリトリプルとは、次の 3 点セットです。 $(T, \eta, \#)$。 おいおい、記号だけ見せられても分かんねーよ。 ということで、どういうことか説明するフリップを用意いたしました。
\begin{eqnarray}
\text{自己函手}&T&:& \mathcal{C} \to \mathcal{C} \\
\text{自然変換}&\eta&:& id_\mathcal{C} \to T \\
\text{写像族}&\#&:& (id_\mathcal{C} \to T) \to (T \to T)
\end{eqnarray}
ジャガーさん(じゃんぐるちほー在住)「全然わからん……」
とりあえず上から見ていきます。
自己函手の前に函手について説明する必要があるでしょう。 そしてその前に圏の説明が必要です。 遠大な道のりだぁ……。
みなさんは数学の時間に函数について学んだことがあると思います。 $y = f(x)$ みたいなやつです。 この函数は $x$ という値を $y$ という値に変換しているといえます。 この例でいう $x$ や $y$ を “対象” といい、対象を別の対象に変換する手続き $f$ を “射” といいます。 そして、すべての対象と射のまとまりを “圏” といいます。
そして函手とは圏と圏の対応付けのことです。 うーん、わかったようなわからないような。 自己函手とは式にもあるとおり同じ圏同士の函手のことですが (あ、$\mathcal{C}$ とは Category すなわち圏のことです)、じゃあその圏ってなんなんだよ。
一言で言えば、モナドの圏における “対象” とは、型のことです。 モナドはある型を別の方に変換します。 モナドの中でも著名な Maybe モナド (いわゆる箱モデルです) は、“中” に他の値が入っています。 Int から Maybe Int に変換することができるわけです。 そして、これらの型はすべて “Haskell の型” という同じ圏 (Hask 圏とよぶそうです) の対象です。 ゆえにモナドは自己函手なのですね。
自然変換 $\eta$ の式にでてくる $id_\mathcal{C}$ ですが、これは圏 $\mathcal{C}$ 上の恒等写像です。 恒等写像っていうのは要するに $f(x) = x$ みたいな引数がそのまま出てくるやつのことです。 圏 $\mathcal{C}$ の対象は “型” なので、たとえば Int 型を突っ込んだら Int 型が、String 型を突っ込んだら String 型が出てくるみたいな変換です。
$\eta$ の式は射と射が対応付けられていますが、これに引数を追加して対象と対象の対応付けに書き換えてみましょう。
\begin{eqnarray}
\text{自然変換} \: \eta&:& id_\mathcal{C} &\to& T \\
\eta_A &:& id_\mathcal{C}(A) &\to& T(A) \\
&:& A &\to& T(A)
\end{eqnarray}
このようにすると言わんとすることが見えてくるのではないでしょうか。 つまり、箱モデルでいうと、“裸の値を箱に入れる” ような動作を示します。 Haskell のような言語ではreturn
やpure
などで示されます。
写像族 $\#$ に関しても同様にして整理します。
\begin{eqnarray}
\text{写像族} \: \#&:& (id_\mathcal{C} \to T) \to (T \to T) \\
\#_{AB}&:& \left[id_\mathcal{C}(A) \to T(B)\right] \to \left[T(A) \to T(B)\right] \\
&:& \left[A \to T(B)\right] \to \left[T(A) \to T(B)\right] \\
\end{eqnarray}
これは要するに、函数の変換を意味します。 型 $A$ の値をとって型 $T(B)$ の値を返す函数 (このような函数を今後モナド函数とよぶことにします) $f_{AB}$ を、型 $T(A)$ の値をとって型 $T(B)$ の値を返す函数 $f_{AB}^\#$ に変換する手続きが $\#$ です。 $A$ と $B$ は同じでも構いません。 箱モデルでいうと、“裸の値をとって箱に入った値を返す函数を、箱に入った値から取り出してもとの函数に入れるような函数に変換する” ような動作を示します。 Haskell のような言語では、演算子>>=
を導入して $f^\#(m)$ のことをm >>= f
とします。 私はこちらのほうが見やすいので今後 $m \bind f$ のように表記します。
モナド則を読み解く
ここまで用意できたので、モナド則を実際に読んでみましょう。 一般に流布されているモナド則は Haskell の文法で示されています。
return x >>= f == f x
m >>= return == m
(m >>= f) >>= g == m >>= (\x -> f x >>= g)
これを数学風に書き直していきましょう。 Haskell は (残念ながら) 誰しも知る言語ではありませんが、数式は人類の共通語です。 ただのフレーバー。
\begin{eqnarray}
\eta(x) \bind f &=& f(x) \\
m \bind \eta &=& m \\
(m \bind f_1) \bind f_2 &=& m \bind \left[\lambda x. f_1(x) \bind f_2\right]
\end{eqnarray}
1 行目を読み解くと、次のように言っています。
任意の値 $x$ を $\eta$ に与えて得られるモナドを任意のモナド函数 $f$ に束縛して得られるモナドは、単に $x$ をモナド函数 $f$ に与えて得られるモナドと等しい。
なんとなく理解できましたか? あ、“なぜそうなるんだろう” とか “そうであることにどういう意味があるんだろう” とかを理解しようとするのではなく、なにを言っているかだけわかれば十分です!
では 2 行目に行ってみましょう。
任意のモナド $m$ を $\eta$ に束縛して得られるモナドは、元のモナド $m$ と等しい。
1 行目と 2 行目は $\eta$ の性質について説明しています。 要するに、束縛という演算において影響を与えないんですね、この函数。
上の方で “なぜそうなるんだろう” に関して考えなくていいと言いましたが、それは $\eta$ がそういう性質をもっているというより、“モナドの設計者がそうなるよう $\eta$ を設計する” からです。
箱モデルでいえば $\eta$ は “値を箱に入れるだけ” なので、そうなるのは至極当然に思えます。 しかし、それがこの法則が成り立つ理由であるとは考えないでください。 しつこいようですが、箱がモナドなのであってモナドが箱なのではありません。 箱というモナドにおいての $\eta$ が “箱に入れる、おわり!” に値するというだけの話です。 “箱に入れるという単純な操作ゆえにこの法則が成り立つ” のではありません。 逆です。
さて、3 行目です。 ここであまり馴染み深くない表記が登場しました。 だけど恐れないで! あなたがプログラマーで、さらに “ラムダ式” というものを扱ったことがあるなら、まさにこれがそうです。 ラムダとはもちろん $\lambda$ のことです。 たとえばラムダ式 $\lambda x. 2x$ は、引数を 2 倍する函数を意味しています。 $\lambda xy. |x - y|$ はふたつの引数の差を得る函数です。 もう怖くありませんね。
では読み解いていきましょう。
任意のモナド函数 $f_1$ と $f_2$ がある。 ここで、任意のモナド $m$ を $f_1$ に束縛して得られたモナドをさらに $f_2$ に束縛して得られるモナドは、“引数を $f_1$ に与えて得られるモナドを $f_2$ に束縛して得られるモナド” を返す新たなモナド函数 $f_{12}$ にモナド $m$ を束縛して得られるモナドと等しい。
うーん、ちょっと長いですね。 ですが、じっくり読み解けばそこまで難しい話ではありません。 要するに、ふたつのモナド函数に順番に束縛するのと、その函数をひとつにしてから束縛するのでは結果が同じ、と言っています。 ああ、もちろん、こういう部分も “そうなるように設計する” だけの話ですよ!
やりましたね! これがモナドのすべてです。
おっとそうだ、“そうであることにどういう意味があるんだろう” と気になったあなたはこの記事を読むと幸せになれるはずです。
ところで、モノイド
少し脱線してモノイドの話でもしましょう。 あ、脱線といってももちろん未来に必要なフラグ立てなので、脱線だからといって読み飛ばさないでくれると助かります。
モノイドは代数的構造のひとつです。 ウープス、またも小難しい言葉が出てきました! だけどあなたの周りにはすでにモノイドが、そしてそれ以外の代数的構造が満ち溢れています。 たとえば、掛け算。 たとえば、文字列結合。 たとえば、USB ハブ。 代数的構造は RPG のモンスターなどではなく、あなたの友達です。
もっとも単純な代数的構造はマグマです。 マグマの称号を得たいあなたは、国際マグマ組合に出かけます。 試験官は言いました。
あなたは、あるカテゴリーのものふたつを組み合わせて同じカテゴリーのものを生み出せますか? また、それはそのカテゴリーのどんなものにも適用できますか? ほほう、掛け算ですか。 合格です。 お気をつけてお帰りください。
OK、整理しましょう。 マグマの要件は、数学的にはこうです。
ある集合 $M$ と、$M$ 上の任意の二元について別の元を対応させる二項演算 $*$ において、積 $M * M$ がふたたび $M$ に属する (演算について閉じている) とき、$(M, *)$ をマグマという。
数 (ここでは複素数としましょう) の掛け算では、数同士を掛けたとき必ず数がでてきます。 $2 \cdot 3 = \text{戦艦ポチョムキン}$ とかにはなりません。 ですので、$(\mathbb{C}, \cdot)$ は立派なマグマです。
マグマ免許を取得すると、次は半群免許に挑戦できます。
マグマ $(S, *)$ が、以下を満たすとき、$(S, *)$ を半群という。
結合律: $\forall a, b, c \in S \; s.t. \; (a * b) * c = a * (b * c)$
数学っぽくなってきましたが、言ってることは簡単です。 $2 \cdot 3 \cdot 4$ を考えてみてください。
ようは、$6$ かける $4$ と、$2$ かける $12$ が一緒ってことだろ?
フーム、数の掛け算は半群でもあるようです。
ではモノイドはどうでしょう?
半群 $(M, *)$ が、以下を満たすとき、単位元 $e$ を加えた$(M, *, e)$ をモノイドという。
単位律: $\exists e, \forall a \in M \; s.t. \; e * a = a = a * e$
なにやら記号が増えてきました。 じゃあ、人間の言葉で話しましょう。
どんな数に左から掛けても右から掛けても元の数と変わらない数 “単位元” が存在するんだよ?
我々はその偉大な存在を知っています。 $1$ です。 $(\mathbb{C}, \cdot, 1)$ はモノイドです。
勇気を出してもう一歩進んでみましょう。 それは群です。
モノイド $(G, *, e)$ が、以下を満たすとき、$(G, *, e)$ を群という。
可逆律: $\exists a^{-1}, \forall a \in G \; s.t. \; a^{-1} * a = a * a^{-1} = e$
つまり、
どんな数にも、掛けたら $1$ になっちゃう数 “逆元” が存在するんだって!
そう、それは逆数です。 $2$ の逆数は $\frac{1}{2}$ です。 $\frac{3}{4}$ の逆数は $\frac{4}{3}$ です。 ということは、掛け算は群でもありますよね?
ちょっとまてよ、ゼロはどうなるんだ?
おっと! そのとおり、$0$ の逆数は存在しません。 ですから、数の掛け算は群ではありませんでした。 しかし、$0$ 以外の数の掛け算は群です。 というのも「$0$ 以外の数の世界」で、掛けたら $0$ になる数はない (閉じている) ので、これもアリなのです。
まとめましょう。 モノイドとは、こんなものです。
- $(M, *, e)$:
- “集合”、“集合の元同士の二項演算”、“集合の元のうちのひとつである単位元” で構成される。
- $(a * b) * c = a * (b * c)$:
- 二項演算の連鎖は、左を先にやっても右を先にやっても同じ。
- $e * a = a, a * e = a$:
- 単位元を含む二項演算の解は、元の元と同じ。
モナド、モノイド説
モナドとモノイド、ずいぶんよく似ているでしょう?(言葉の響き的な意味で)
しかし、モナドには二項演算が見当たりません。 $\bind$ はモナドとモナド函数をとってモナドを返す演算ですし。
ところがどっこい、モナド則の 3 行目を御覧ください。 $\lambda x. f_1(x) \bind f_2$ の部分ですよ。
モナド函数とモナド函数をくっつけて、モナド函数が生み出されています。 これは、モナド函数の集合 $F$ 上の二項演算と言えないでしょうか?
ということで、演算子 $\composekleisli$ を導入します。
\lambda x. f_1(x) \bind f_2 \to f_1 \composekleisli f_2
この演算子を導入することで、以下のような書き換えが可能になります。
f_1(x) \bind f_2 \to (f_1 \composekleisli f_2)(x)
さらにモナド $m$ をモナド函数 $f$ の返り値 $f(x)$ と置き換え、また任意の入力についてそれぞれ同じ出力を返す二つの函数は等しいとしてモナド則を一気に書き換えていきます。 すると、驚きの結果が!
\begin{eqnarray}
\eta \composekleisli f &=& f \\
f \composekleisli \eta &=& f\\
(f_1 \composekleisli f_2) \composekleisli f_3 &=& f_1 \composekleisli (f_2 \composekleisli f_3)
\end{eqnarray}
ワオ! これはモノイドそのものです! モナドは $(F, \composekleisli, \eta)$ なモノイドだったのです!
つまり
モナドは単なる自己函手の圏におけるモノイド対象だよ。 なにか問題でも?
……と、言いたいところなのですがこの記事で検証してきたことと、この暴言名言の意図するところは微妙に違うようです。 気になる方は元ネタの書籍を読むか、圏論を勉強してこの記事を読んでみましょう。
余談: クライスリトリプルにおけるモナド則
クライスリトリプルにおいての説明ではスルーしましたが、モナド則はクライスリトリプルにおける要求そのものでもあります。
クライスリトリプルでは、$\eta$ と $\#$ について以下のことが要請されます。
\begin{eqnarray}
f^\# \circ \eta_A &=& f \\
\eta_A^\# &=& id_{T(A)} \\
f_2^\# \circ f_1^\# &=& (f_2^\# \circ f_1)^\# \\
\end{eqnarray}
これはモナド則と対応しています。 函数の等式なので対象 $(x), (m), (m)$ を両辺に後置して式変形していくとそれがわかります。 試しにやってみてください。
第 1 式は、 第 2 式は、 第 3 式は、
解答
\begin{eqnarray}
f^\# \circ \eta_A &=& f \\
(f^\# \circ \eta_A)(x) &=& f(x) \\
f^\#\left[\eta_A(x)\right] &=& f(x) \\
\eta_A(x) \bind f &=& f(x) \\
\end{eqnarray}
\begin{eqnarray}
\eta_A^\# &=& id_{T(A)} \\
\eta_A^\#(m) &=& id_{T(A)}(m) \\
m \bind \eta_A &=& m \\
\end{eqnarray}
\begin{eqnarray}
f_2^\# \circ f_1^\# &=& (f_2^\# \circ f_1)^\# \\
(f_2^\# \circ f_1^\#)(m) &=& (f_2^\# \circ f_1)^\#(m) \\
f_2^\#\left[f_1^\#(m)\right] &=& m \bind f_2^\# \circ f_1 \\
f_2^\#\left[m \bind f_1\right] &=& m \bind \left\{\lambda x. f_2^\#\left[f_1(x)\right]\right\} \\
(m \bind f_1) \bind f_2 &=& m \bind \left[\lambda x. f_1(x) \bind f_2\right]
\end{eqnarray}
余談: “裸の値” というモナド (“なにもしない”というモナド)
さて、ここまで何度も何度も箱モデルを引用したせいで、“やはりモナドは箱なのではないか” と思った方も多いのではないかと思います。
しかし、その箱の中に入る “裸の値” もまたモナドと言えます。 順に見ていきましょう。
自己函手 $T$ ですが、箱モデル (Maybe) では Int などの裸の値 (の型) を Maybe Int に変換しました。 しかし、裸の値 Int をそのまま裸の値 Int にする (ようはなにもしない) 函手もまた Hask 圏の自己函手と言えます。
自然変換 $\eta$ も、同じ型を返す射となります。 $\eta(x)$ は単なる恒等写像 $id(x) = x$ です。
写像族 $\#$ も恒等写像 $id\left[f(x)\right] = f(x)$ です。 なぜなら $T(A) = A$ だからです。 結果として演算子 $\bind$ は、単なる函数適用 $f(x) \to x \bind f$ となります。
演算子 $\composekleisli$ は、演算子 $\bind$ の定義より、ただの函数合成です。 $f_1(x) \bind f_2 \equiv f_2\left[f_1(x)\right] \equiv (f_2 \circ f_1)(x) \to (f_1 \composekleisli f_2)(x)$ ですね。
当然、これらはモナド則 / モノイド則を満たします (結合律に関してはあまり直感的ではないと思いますが、“函数合成 結合律” などでググると証明が出てきます)。
箱どころか、ただの値さえモナドなのです。