私はモナドとは何か、unit
とbind
の働きを説明する多くの投稿を読んだことがあります。それらのいくつかは、カテゴリ理論に直接入り込んでいて、(少なくとも私にとっては)目を惹きます出血、それを完全に無視し、ブリトー、箱、そして何でないかの奇妙な類似に触れている人もいます。
数週間の研究とたくさんのフライドニューロンの後、(私は思う)モナドがどのように機能するか理解しました。しかし、私の理解を免れることがまだ1つあります。実際に触れている投稿はほとんどありません(IOおよび状態を除く)。
なぜ?
なぜモナドは重要なのですか?なぜそんなに重要なのですか?彼らが解決している問題は何ですか?これらの問題はモナドでのみ解決できますか、それとも他の方法がありますか?
何かを解決するためにneedモナドを使用しないでください。特定の事柄を単純化するだけです。モナドを説明するとき、多くの人々があまりにも抽象的で理論的になりすぎています。ほとんどの場合、モナドはプログラミングで何度も登場するパターンです。そのパターンを認識することで、コードを簡略化し、特定の関数の再実装を回避できます。
Haskellの場合、具体的な観点から見ると、モナドで可能な最も目に見えるものは do notation です。 List comprehensions Haskellや他の言語でもモナドを利用しています。 Control.Monad のようなライブラリを作成することもできます。
これらはすべて便利な簡略化を提供し、1つのモナドに実装すると、allモナドに自動的に適用されます。これは、コードの再利用が他のパラダイムよりも関数型プログラミングの方がはるかに簡単である主な理由の1つです。
特定のモナドを見て、それらが解決する問題を確認すると、理解しやすくなります。たとえば、Haskellの場合:
IO:IOを型システムで表すことができるため、IOを実行する関数から純粋な関数を明確に分離できます。
リスト:リスト内包表記とノード決定論的計算を行うことができます。
多分:nullのより良い代替手段であり、C#のnull融合演算子に相当するものをサポートしています。
Parsec:パーサーを作成するための便利なDSL。
したがって、個々のモナドの根拠は、どれも非常に便利なので、簡単に(私は願っています)わかります。しかし、それらが解決する問題もまったく異なり、一見すると、操作をチェーンするためのいくつかのロジックに関連していることを除いて、共通点があまりないようです。モナドは、このようなさまざまなツールを構築できるので便利です。
上記の例はモナドなしで実装できますか?確かにそれらはアドホックな方法で実装できますが、モナドを言語に組み込むことで、すべてのモナドで機能するdo
- notationのような直接の言語サポートが可能になります。
混乱を招く1つの点は、bind
や_<*>
_などの「人気のある」関数は、実践指向であることです。しかし、概念を理解するために、最初に他の関数を見る方が簡単です。モナドが他の関連概念と比較して少し誇張されているため、モナドが目立つことも注目に値します。代わりにファンクタから始めます。
ファンクタは、関数(Haskell表記)fmap :: (Functor f) => (a -> b) -> f a -> f b
を提供します。つまり、関数をリフトインできるf
コンテキストがあります。あなたが想像できるように、ほとんど何でもファンクターです。リスト、たぶん、どちらか、関数、I/O、タプル、パーサー...それぞれが、値が表示されるコンテキストを表します。したがって、fmap
またはそのインラインバリアント_<$>
_を使用すると、ほとんどすべてのコンテキストで機能する非常に用途の広い関数を作成できます。
コンテキストで他にしたいことは何ですか? 2つのコンテキストを組み合わせることができます。そのため、たとえばZip :: [a] -> [b] -> [(a,b)]
の一般化を取得したい場合があります:pair :: (Monoidal f) => f a -> f b -> f (a,b)
。
しかし、実際にはさらに便利であるため、Haskellライブラリは、Applicative
とFunctor
の組み合わせであるMonoidal
を提供します。また、Unit
も提供します。これにより、unit
でコンテキストの「内部」に値を置くことができます。
作業中のコンテキストについてこれら3つのことを述べるだけで、非常に一般的な関数を作成できます。
Monad
は、その上で述べることができるもう1つのことです。前に触れなかったことは、2つのコンテキストを組み合わせる2つの方法があることです。それらをpair
するだけでなく、スタックすることもできます。リストのリストを持つことができます。 I/Oコンテキストでは、例として、ファイルから他のI/Oアクションを読み取ることができるI/Oアクションがあるため、タイプはFilePath -> IO (IO a)
になります。実行可能関数_IO a
_を取得するために、どのようにそのスタックを取り除くことができますか?これがMonad
s join
の出番です。同じタイプの2つのスタックされたコンテキストを組み合わせることができます。同じことが、パーサー、多分などにも当てはまります。そして、bind
は、join
を使用するより実用的な方法です
したがって、モナディックコンテキストは4つのことを提供するだけでよく、I/O、パーサー、障害などのために開発されたほとんどすべての機構で使用できます。
モナドを使用すると、さまざまな純粋でない計算を表現できるだけでなく、コードを簡略化できます
そして、重要なのは、純粋な言語構造に妥協することなく、よりクリーンな言語を利用することなく