プログラミングに出てくるモナドとはよくわからないなにか、あるいはHaskellの危険地帯である。
まとめ
- モナドには種類があり、種類ごとに意味が異なる。「モナド」とひとくくりに理解することはできない。
- 各種モナドは、それぞれのAPIを理解できれば他のライブラリと同様に扱えばいい。
- よってこの記事は無駄である。
記事の概要
この記事を開いたあなたはきっと、「今度こそよくわからないモナドをきちんとわかるように解説してくれるページが現れたかもしれない(たぶん違うんだろうな)」と思ってページを開いたことと思う。
すまない。この投げやりな冒頭にさぞ手抜きだ立て逃げだと憤ったことであろう。しかし、モナドの説明に深入りしたあなたは、あちこちを調べて回った挙句、この冒頭に憤ったことも忘れてこう思うかもしれない。もうよくわからないなにかでいいや、と。
以下の記事を読んで「なんだ簡単じゃないか、脅かしやがって」と思えたなら、この記事の説明がよその記事よりは良かったということいやむしろ読者の理解力を褒め称えるべきであり、「もうよくわからないなにかでいいや」と思ったらこの冒頭は間違っていなかったことになる。
なお、最近では関数型プログラミングにモナドの理解は必要ないという意見が主流を占めつつあるように思える。当記事の初稿編集者も同意見で、モナドに属する個々の型・クラスのAPIドキュメントを理解することが必要かつ十分だと思うので引き返すなら今のうちである。
モナドに言語レベルでのサポートがあるのはHaskellくらいなので、他のプログラミング言語でモナドを知る必要性はさらに薄いといえる。
なお、この記事はやたら長くなっているが、それでもモナドの理論の入り口くらいまでで、実践するところまではカバーしていない。いくらやっても入り口から先に進むことが出来ないから、少々手間でも入り口くらいには立ってみたい。そういう人向けの記事でありたいけどこめん無理wwwww。
引き返す
モナドの理解には関わらない以上、たとえ以下の記事の内容がこの記事よりも優れていたとしても以下のリンクには進まないのが賢明な判断というものである。
- 箱で考えるFunctor、ApplicativeそしてMonad: 直観的イメージをつかみやすいが、応用を妨げる誤解を生む。
- モナドはメタファーではない: ある程度理解した上で読むと、共感できることが書かれている。
- モナドは単なる自己関手の圏におけるモノイド対象だよ。何か問題でも?: モナドを分かっている人がわかっていない人を笑うとき以外に用途が見いだせない。
説明開始とみせかけて更に引き止める
モナドに対しての関わり方には以下の3通りがある。
モナドを使うのが目的であれば、今からでも遅くはない。ブラウザの戻るボタンを押して使いたいモナドのAPIドキュメントを読んだほうが良い。
モナドなクラスを設計するのが目的であれば、そんなことにこだわるより、便利なクラスになるように心がけた方が良い。必要であればそれは自然にモナドになるし、必要がなければそうはならない。きっとそんなものだと思う。
モナドが何であるかを理解するという問題については、そもそもそれが可能かというところから検討しなければいけないかもしれない。
ちまたにあふれる説明が分かりにくい理由
説明
モナドの概要
いろいろな人がいろいろな解説を試みており、モナドとは象である(だれも全貌を把握できない)とか、モナドとはモナドである(モナドと同じ性質を持つものはモナドしかないという意味で、必ずしも説明を放棄しているわけではないと思われる。)といったいかがわしい説明まで出る始末である。
プログラミングにおけるモナドとは、プログラミングにおける一つの定型的手法(デザインパターン)が、Hakellでいうところの型クラス、Scalaでいうところのtrait(英語で特性という意味でJavaの抽象クラスが多重継承可能になったようなもの)、Javaでいうところのインターフェースで提供されるものだといえる。
もうこの時点で、モナドの意味を考えるという行為に疑問を抱かねばならない。デザインパターンに「意味」を求めたりするだろうか。仮に求めているとして、同じ種類の意味をモナドに求めようとしているだろうか。
モナドには種類があり、個々のモナドごとに違った意味を持っている。個々のモナドの意味を通り越して、全てのモナドに共通の意味を求めようとすることが不幸の始まりなのである。
ラムダ式
モナドを語るのに高階関数は欠かせない。そしてそれを表現する手段としてラムダ式の表現方法はないとかえって煩雑になる。ラムダ式の書き方はプログラミング言語により多少差があるが、ここでは
f(x) = x2 + x + 1のとき、f = λ x → x2 + x + 1
という表記法をとることにする。
型
型を意識せざるを得ないのだが、どう表記しよう。ジェネリクスとかタプルとか。
unitA(x): A[X]
unitA: X → A[X]
と書くことにする。
モナドの説明
まず、モナドとはMaybeモナドやStateモナドなどの総称であり、たとえばMaybeモナドという個々のモナド自体が、Maybeモナドのモナド則を満たすものの総称である。つまりモナドは総称の総称ということになる。
モナド則を満たす一番単純でわかりやすい実装は、上記リンク先でも取り上げられているデータを格納した箱である。実際たいていの実装は何らかの形で内部にモナドの元になるデータを保持している。
また、値の定まっていない変数なのか、代入されて値が固定されているのかが字面からは判断しづらいことも混乱に拍車をかけているように思える。
2つの関数
たとえば仮にAモナドというものを考える。
任意の値xに対応する(1対1とは限らない)AモナドAxが存在する。1対1とは限らないので、この記法は問題があるどころかこの記法でついた先入観から脱却するのが困難になるのだがとりあえず目をつぶることにする。
unit()
ここで関数unit(x)を考える。Haskellのreturnに相当。モナドごとに異なる関数なので、再代入により変数名を使いまわしたりしない関数型プログラミングの考え方を参考にAモナドのunit()をunitAと書くことにする。
unitA(x) = Ax (Haskellだとretrun x == Ax)という定義になる。
とりあえず、x を A という箱の中に入れるイメージでよい。(モナドにするときに情報が維持される場合以外に、情報が増える場合と減る場合があり、箱モデルではその点については誤解が生じやすい。ちゃんと通用するのはIdentityやMaybe(Option)モナドなど、xとAxに一対一対応がある時くらいで、モナドの種類によってはすぐに限界を露呈し、かえって理解の妨げになる。後で補足する。)
x
x
= Ax
flatMap()
もう一つflatMap(これもモナドごとに異なる関数である)という高階関数を考える。Haskellの>>=に相当する。
箱モデルはいけないと思いつつも、イメージをつかむ方法が他にないので、箱モデルで説明すると、
f(x) = Ayのとき、
flatMapA(Ax, f) = Ay (HaskellだとAx >>= f == Ay) = unitA(y)
x
mapA(f)
→
f(x) = Ay
=
→
y
y
箱の中でxに関数fを適用すると、箱の中に、箱に入ったyが出てくる。いうなればAAyの状態である。flatMapではAAyの状態をAyにする。AAyをAyにすることをflatにする(flatten)と呼ぶ。納得行かなくてもそう呼ぶ慣習になっているので。
map(これもモナドごとに異なる関数(ry)という関数が名前の元になっている。
g(x) = y のとき
Ax(たりうるものの集合)からAy(たりうるものの集合)への写像(map)になっているので、こう呼ぶ。
mapしてflattenするという考え方から、flatMapという関数名で扱う言語が多い(Haskellはそうではない)。
unit()同様とりあえず把握するために箱モデルで説明しているが、このモデルで考えるとかえって混乱するモナドが多いので、あくまでもとりあえずの理解のためと割りきっておくこと。
AAyをAyにできるのだから、Ayをyにすることも出来そうだが、それができることは保証されない。
また説明の都合上map関数を定義したが、map関数が定義できることは保証されていない。flatMapから合成できるとか何とか。
箱モデルだとAxからxを取り出せそうに見えるし、実際取り出せるモナドが多いのだが、モナドの性質としては保証されない。後述の定数モナドを参照のこと。
にわとりとたまご
unit()もflatMap()もそういう関数が「ある」というのではなく、モナド則を満たすようにunit()とflatMap()を「定義する」のである。unit()ともかく箱モデルのflatMap()はunit(x)で得られるモナドには通用するものの、それでは説明できない実装の方が多い。モナド則さえ満たせれば箱モデルの実装である必要は全くない。
モナドという単語で、上記で言うAxのようなデータを指すことも多いが、データ型のみではモナドになることはできない。unit()とflatMap()を組み合わせてはじめてモナドと呼べるのである。
Haksellでのモナドの意味
宣言型プログラミングにおいて手続きの順番は指定できない。順番を指定したければ関数適用の順番によってするしかないのである。
文脈
モナドは文脈を持った計算という表現をされることが多々ある。
強いて言うならば、モナドにするときに情報が増えたり減ったりするが、情報が増えたこと、減ったことを気にせずに、計算を続けたいという希望を叶えることができる。しかし、値を取り出そうとすると最終的にはその情報の増減には向き合わねばならない。
モナド合成
モナドの闇である。
ABx と BAxは一致しない。
たとえば、List<Optional<T>>とOptional<List<T>>を考えれば分かるように、前者は個々の要素について値がある場合とない場合があるが、後者は要素全てに値があるか、要素が存在しないかの2通りしかない。
解決策として以下のようなものが提唱されているらしいが、普及はしていないようだ。
子記事
関連項目
- Haskell
- 関数型言語
- 関数型プログラミング
- 圏論
- 高階関数 / ラムダ式
- モナド / モナド(数学)
- モノイド
- Promise(JavaScript)
- 考えたら負け
- プログラミング関連用語の一覧
- 5
- 0pt