モナドとは何ですか?


1415

最近Haskellを手短に見てきたが、モナドが本質的に何であるかに関して、簡潔で簡潔で実用的な説明は何だろうか?

私が遭遇したほとんどの説明は、かなりアクセスしにくく、実用的な詳細に欠けていることがわかりました。


12
Eric Lippertがこの質問への回答を書きました(stackoverflow.com/questions/2704652/…)。これは別のページにあるいくつかの問題が原因です。
Pは

70
これが JavaScriptを使用した新しい紹介です-とても読みやすいと思いました。
Benjol 2011年



2
モナドはヘルパー操作を持つ関数の配列です。この回答を
cibercitizen1 2014

回答:


1060

最初:数学者でない場合、モナドという用語は少し空虚です。別の用語は、それらが実際に何のために役立つかをもう少し説明する計算ビルダーです。

あなたは実用的な例を求めます:

例1:リスト内包表記

[x*2 | x<-[1..10], odd x]

この式は、1〜10の範囲のすべての奇数の倍数を返します。非常に便利です。

これは、実際にはListモナド内の一部の操作の単なる構文糖であることがわかります。同じリスト内包表記は次のように書くことができます。

do
   x <- [1..10]
   guard (odd x)
   return (x * 2)

あるいは:

[1..10] >>= (\x -> guard (odd x) >> return (x*2))

例2:入力/出力

do
   putStrLn "What is your name?"
   name <- getLine
   putStrLn ("Welcome, " ++ name ++ "!")

どちらの例もモナド、別名計算ビルダーを使用しています。共通のテーマは、モナドが特定の有用な方法で操作連鎖させることです。リスト内包表記では、操作が連鎖され、操作がリストを返す場合、リスト内のすべてのアイテムに対して次の操作が実行されます。一方、IOモナドは操作を順次実行しますが、「隠された変数」を渡します。これは、「世界の状態」を表し、純粋な機能的な方法でI / Oコードを記述できます。

連鎖操作のパターンは非常に便利で、Haskellのさまざまなものに使用されています。

別の例は例外です。Errorモナドを使用すると、エラーがスローされた場合を除いて、操作が順番に実行されるように操作がチェーンされます。エラーがスローされた場合、残りのチェーンは破棄されます。

リスト内包構文とdo表記はどちらも、>>=演算子を使用して操作を連鎖させるための構文糖衣です。モナドは基本的に、>>=演算子をサポートする単なる型です。

例3:パーサー

これは、引用符で囲まれた文字列または数値を解析する非常に単純なパーサーです。

parseExpr = parseString <|> parseNumber

parseString = do
        char '"'
        x <- many (noneOf "\"")
        char '"'
        return (StringValue x)

parseNumber = do
    num <- many1 digit
    return (NumberValue (read num))

操作chardigitなどが非常にシンプルです。それらは一致するか、一致しません。マジックは、制御フローを管理するモナドです。操作は、一致が失敗するまで順次実行され<|>ます。この場合、モナドは最新のものに戻り、次のオプションを試みます。繰り返しになりますが、いくつかの追加の有用なセマンティクスで操作をチェーンする方法。

例4:非同期プログラミング

上記の例はHaskellにありますが、F#もモナドをサポートしていることがわかります。この例はDon Symeから盗まれたものです。

let AsyncHttp(url:string) =
    async {  let req = WebRequest.Create(url)
             let! rsp = req.GetResponseAsync()
             use stream = rsp.GetResponseStream()
             use reader = new System.IO.StreamReader(stream)
             return reader.ReadToEnd() }

このメソッドは、Webページをフェッチします。パンチラインは次の用途に使用さGetResponseAsyncれます。メインスレッドが関数から戻る間、実際には別のスレッドで応答を待ちます。最後の3行は、応答が受信されたときに、生成されたスレッドで実行されます。

他のほとんどの言語では、応答を処理する行に対して個別の関数を明示的に作成する必要があります。asyncモナドは、独自のブロック「分割」することが可能であり、後半の実行を延期します。(async {}構文は、ブロック内の制御フローがasyncモナドによって定義されることを示しています。)

それらがどのように機能するか

それで、モナドはこれらのすべての派手な制御フローのことをどのように行うことができますか?doブロック(またはF#で呼び出される計算式)で実際に発生するのは、すべての操作(基本的にすべての行)が個別の無名関数でラップされることです。これらの関数は、bind演算子を使用して結合されます>>=(Haskellでスペル)。bind操作は関数を組み合わせているので、適切と思われるように関数を実行できます。順次、複数回、逆に、一部を破棄し、別のスレッドで実行したい場合は別のスレッドで実行します。

例として、これは例2のIOコードの拡張バージョンです。

putStrLn "What is your name?"
>>= (\_ -> getLine)
>>= (\name -> putStrLn ("Welcome, " ++ name ++ "!"))

これは醜いですが、実際に何が起こっているのかはより明白です。>>=オペレータは、魔法の成分である:これは、新しい値を生成するために、(左側)の値をとり、(右側)の機能とそれを兼ね備えています。次に、この新しい値が次の>>=演算子によって取得され、関数と組み合わせられて新しい値が生成されます。>>=ミニエバリュエーターと見なすことができます。

>>=すべてのモナドは、独自の実装を持っているので、さまざまな種類の過負荷になっています>>=。(ただし、チェーン内のすべての演算は同じモナドの型でなければ>>=なりません。そうでないと、演算子は機能しません。)

の可能な最も単純な実装は>>=、左側の値を取り、それを右側の関数に適用して結果を返しますが、前述のように、モナドの実装で何か特別なことが行われている場合、パターン全体が役立つのは>>=

値が1つの操作から次の操作に渡される方法にはいくつかの追加の巧妙さがありますが、これにはHaskellの型システムのより深い説明が必要です。

まとめ

Haskell用語では、モナドはモナド型クラスのインスタンスであるパラメータ化された型であり、>>=他のいくつかの演算子とともに定義されます。簡単に言えば、モナドは>>=演算が定義される型にすぎません。

それ自体>>=は関数をチェーン化する厄介な方法ですが、「配管」を隠すdo表記が存在するため、モナディック演算は非常に素晴らしく有用な抽象化であり、言語の多くの場所で有用であり、有用です言語で独自のミニ言語を作成するため。

なぜモナドは難しいのですか?

多くのHaskell学習者にとって、モナドはレンガの壁のようにぶつかる障害物です。モナド自体が複雑であるというわけではありませんが、パラメーター化された型、型クラスなど、他の多くの高度なHaskell機能に実装が依存しているのです。問題は、Haskell I / Oがモナドに基づいていることです。I/ Oは、おそらく新しい言語を学ぶときに最初に理解したいことの1つです-結局のところ、何も生成しないプログラムを作成するのはそれほど楽しくありません。出力。言語の他の部分について十分な経験を積むまで、「ここで魔法が起こる」のようにI / Oを扱うことを除いて、私はこの鶏と卵の問題に対する直接の解決策はありません。ごめんなさい。

モナドに関する優れたブログ:http : //adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html


66
モナドを理解するのに多くの問題を抱えている人として、私はこの答えが少し助けになったと言えるでしょう。しかし、まだわからないことがいくつかあります。リストはモナドをどのように理解していますか?その例の拡張形式はありますか?これを含むほとんどのモナドの説明について本当に気になるもう1つのことは、「モナドとは何ですか?」「モナドは何のために良いのですか?」そして「モナドはどのように実装されていますか?」「モナドは基本的には、>> =演算子をサポートする型にすぎません」と書いたときにそのサメに飛びつきました。ちょうど私にあった...
ブルトン

83
また、モナドが難しい理由についてのあなたの結論にも同意しません。モナド自体が複雑でない場合は、手荷物なしでそれらが何であるかを説明できるはずです。「モナドとは何か」という質問をするとき、実装について知りたくありません。それが引っ掻き傷のつもりであるかゆみを知りたいのです。これまでのところ、答えは「ハスケルの作者はサドマゾヒストであり、単純なことを達成するために愚かな複雑なことをするべきだと決めたので、モナドがハスケルを使用することを学ぶ必要があります。自身」...
ブルトン語

70
しかし、それは正しくないかもしれませんね。モナドは難しいと思います。実装の詳細の混乱に巻き込まれずにモナドを説明する方法を理解できる人はいないからです。スクールバスって何?これは、精製された石油製品を消費して一部の金属製ピストンをサイクルで駆動し、次にいくつかの車輪を駆動するいくつかのギアに取り付けられたクランクシャフトを回転させる装置を前面に備えた金属製プラットフォームです。ホイールの周りにはゴム製のバッグがあり、これはアシュファルトの表面と接触してシー​​トの集まりを前方に移動させます。次の理由により、座席は前に移動します...
ブルトン

130
私はこれをすべて読んでもモナドが何であるかはわかりませんが、それはHaskellのプログラマーが説明するのに十分に理解していないものであるという事実は別です。これらはモナドなしで実行できるすべてのものであることを考えると、例はあまり役に立ちません。この回答では、モナドがどのようにモナドを簡単にするのかがわかりにくく、混乱を招くだけです。この回答の有用性に近づいたのは、例2の構文糖が削除された部分です。最初の行を除いて、拡張はオリジナルと実際の類似点を何も持っていないので、私は接近したと言います。
Laurence Gonsalves、2011

81
モナドの説明に固有であると思われるもう1つの問題は、Haskellで記述されていることです。Haskellが悪い言語だと言っているのではありません-それはモナドを説明するのに悪い言語だと言っています。Haskellを知っていれば、私はすでにモナドを理解しているので、モナドを説明したい場合は、モナドを知らない人が理解しやすい言語を使用することから始めます。Haskellを使用する必要がある場合は、構文糖度をまったく使用しないでください。できる限り最小かつ最も単純な言語のサブセットを使用し、Haskell IOの理解を前提としないでください。
Laurence Gonsalves、2011

712

「モナドとは」を説明するのは、「数とは」と言うのに少し似ています。私たちはいつも数字を使います。しかし、数字について何も知らない人に出会ったと想像してみてください。どのように一体あなたは数字が何であるかを説明するだろうか?そして、それがなぜ有用であるかもしれない理由をどのように説明し始めますか?

モナドとは何ですか?簡単に言えば、これは操作を連鎖させる特定の方法です。

本質的には、実行ステップを記述し、それらを「バインド関数」でリンクしています。(Haskellでは、この名前は>>=です。)バインド演算子への呼び出しを自分で書くか、コンパイラーに関数呼び出しを挿入させる構文シュガーを使用できます。ただし、どちらの方法でも、このバインド関数の呼び出しによって各ステップが分離されます。

したがって、バインド関数はセミコロンのようなものです。プロセスのステップを分離します。bind関数の仕事は、前のステップからの出力を受け取り、それを次のステップにフィードすることです。

難しすぎませんか?しかし、モナドには複数の種類があります。どうして?どうやって?

まあ、bind関数 1つのステップの結果を取得し、それを次のステップに送ることができます。しかし、それが「すべて」である場合、モナドは...それは実際にはあまり役に立ちません。そして、それを理解することは重要です:すべての有用なモナドは、単にモナドであることに加え、他の何かをします。すべての有用なモナドには「特別な力」があり、それがユニークです。

何も特別なことをしないモナドは「恒等モナド」と呼ばれます。恒等関数のように、これはまったく無意味なことのように聞こえますが、実際にはそうではありません...しかし、それは別の話です。)

基本的に、各モナドには独自のbind関数の実装があります。また、実行ステップ間で動作するようにバインド関数を記述できます。例えば:

  • 各ステップが成功/失敗のインジケーターを返す場合、前のステップが成功した場合にのみ、バインドで次のステップを実行できます。このようにして、失敗したステップは、ユーザーからの条件付きテストなしで、シーケンス全体を「自動的に」中止します。(失敗モナド。)

  • このアイデアを拡張して、「例外」を実装できます。(エラーモナドまたは例外モナド。)言語機能ではなく、自分で定義するため、それらがどのように機能するかを定義できます。(たとえば、最初の2つの例外を無視し、3番目の例外がスローされたときにのみ中止したい場合があります。)

  • 各ステップで複数の結果を返すようにして、バインド関数をループさせ、それぞれを次のステップに渡すことができます。このようにして、複数の結果を処理するときにループをどこにでも書き続ける必要はありません。bind関数は、「自動的に」すべてを行います。(リストモナド。)

  • あるステップから別のステップに「結果」を渡すだけでなく、bind関数に追加のデータを渡すこともできます。このデータはソースコードに表示されなくなりましたが、手動ですべての関数に渡す必要がないため、どこからでもアクセスできます。(読者モナド。)

  • 「余分なデータ」を差し替えられるようにすることができます。これにより、実際に破壊的な更新を行わなくても、破壊的な更新シミュレートできます。(状態モナドとそのいとこ作家モナド。)

  • 破壊的な更新をシミュレートしているだけなので、実際の破壊的な更新では不可能なことを簡単に行うことができます。たとえば、最後の更新元に戻したり、以前のバージョンに戻すことができます。

  • 計算を一時停止できるモナドを作成できるので、プログラムを一時停止し、内部状態データをいじってから再開できます。

  • 「継続」をモナドとして実装できます。これにより、人々の心壊すことができます

これ以上のすべてはモナドで可能です。もちろん、これらすべてはモナドがなくても完全に可能です。モナドを使用するほうはるかに簡単です。


13
私はあなたの答えに感謝します。特に、これすべてがモナドなしでも当然可能であるという最後の譲歩です。留意すべき点の1つは、モナドを使用する方簡単なことですが、モナドを使用しない場合ほど効率的ではないことがよくあります。トランスフォーマーを使用する必要がある場合、関数呼び出し(および作成された関数オブジェクト)の追加の階層化には、見やすく制御しにくいコストがかかり、巧妙な構文では見えなくなります。
seh

1
少なくともHaskellでは、モナドのオーバーヘッドのほとんどがオプティマイザによって取り除かれます。したがって、唯一の本当の「コスト」は、必要な脳力にあります。(「保守性」が気になるものである場合、これは重要ではありません。)しかし、通常、モナドは物事をより簡単にし、難しくしません。(そうでなければ、なぜあなたは気になるのですか?)
MathematicalOrchid

Haskellがこれをサポートしているかどうかはわかりませんが、数学的には>> =を使用してモナドを定義し、returnまたはjoin and apを使用して定義できます。>> =とreturnはモナドを実用的にするものですが、joinとapはモナドが何であるかをより直感的に理解します。
ジェレミーリスト14

15
数学や関数以外のプログラミングの背景から来た私にとって、この答えは最も理にかなっています。
jrahhali 2017

10
これは、モナドが一体何であるかについて実際に私にいくつかの考えを与えた最初の答えです。それを説明する方法を見つけてくれてありがとう!
robotmay 2017

186

実際、モナドの一般的な理解に反して、彼らは国家とは何の関係もありません。モナドは単に物をラップする方法であり、ラップされたものをアンラップせずに操作を行うメソッドを提供します。

たとえば、Haskellで別のタイプをラップするタイプを作成できます。

data Wrapped a = Wrap a

私たちが定義するものをラップする

return :: a -> Wrapped a
return x = Wrap x

アンラップせずに操作を実行するには、たとえば関数があるとします。これを行うと、ラップされた値に作用するようにその関数を持ち上げるf :: a -> bことができます。

fmap :: (a -> b) -> (Wrapped a -> Wrapped b)
fmap f (Wrap x) = Wrap (f x)

理解することはこれで全部です。ただし、このリフティングを行うためのより一般的な機能があることがわかりますbind

bind :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b)
bind f (Wrap x) = f x

bindは少しより多くのことができますが、fmapその逆はできません。実際にfmapは、bindおよびに関してのみ定義できますreturn。したがって、モナドを定義するときは、その型(ここではWrapped a)を指定し、モナドreturnbind演算の動作を指定します。

クールなのは、これがあちこちに出現する一般的なパターンであることが判明し、純粋な方法で状態をカプセル化することはそのうちの1つにすぎないことです。

HaskellのIOモナドで使用されているように、モナドを使用して機能の依存関係を導入し、評価の順序を制御する方法に関する優れた記事については、IO Insideをチェックしてください。

モナドの理解については、あまり気にしないでください。興味のあることを読んでください。すぐに理解できなくても心配はいりません。次に、Haskellのような言語でダイビングするだけです。モナドはこれらの事柄の1つであり、練習によって理解が脳に入り込み、ある日突然あなたはそれらを理解していることに突然気づきます。


->は右結合のミラー関数アプリケーションで、左結合なので、括弧を省略してもここでは違いはありません。
Matthias Benkard、2008年

1
これはとても良い説明だとは思いません。モナドは単にA方法ですか?大丈夫、どちらの方法ですか?モナドの代わりにクラスを使用してカプセル化しないのはなぜですか?
ブルトン語

4
@ mb21:括弧が多すぎることを指摘しているだけの場合は、a-> b-> cは実際にはa->(b-> c)の短縮形にすぎないことに注意してください。この特定の例を(a-> b)->(Ta-> Tb)と書くと、厳密には不要な文字が追加されるだけですが、fmapがタイプaの関数をマップすることを強調しているため、道徳的に「正しいこと」です-> bタイプTa-> Tbの関数に。そして元々、それはカテゴリー理論においてファンクターがすることであり、そこからモナドが生まれます。
Nikolaj-K

1
この答えは誤解を招くものです。一部のモナドには、固定値からの関数など、「ラッパー」がまったくありません。

1
@DanMandelモナドは、独自のデータ型ラッパーを提供するデザインパターンです。モナドは、ボイラープレートコードを抽象化する方法で設計されています。したがって、コードでモナドを呼び出すと、心配する必要のないものを裏で実行します。Nullable <T>またはIEnumerable <T>について考えてみてください。それらは裏で何をしていますか?ザッツモナド。
sksallaj

168

しかし、あなたはモナドを発明したかもしれません!

sigfpeさんのコメント:

しかし、これらはすべて、説明が必要な難解なものとしてモナドを導入しています。しかし、私が主張したいのは、それらはまったく難解ではないということです。実際、関数型プログラミングでさまざまな問題に直面すると、容赦なく、特定のソリューションに導かれることになりますが、そのすべてがモナドの例です。実際、まだお持ちでない場合は、すぐに発明していただければ幸いです。これらのソリューションはすべて、実際には変装された同じソリューションであることに気づくための小さなステップです。そして、これを読んだ後は、モナドに関する他のドキュメントを理解する方が良いかもしれません。

モナドが解決しようとする問題の多くは、副作用の問題に関連しています。それでは、それらから始めましょう。(モナドを使用すると、副作用の処理以上のことができることに注意してください。特に、多くの種類のコンテナオブジェクトをモナドと見なすことができます。もう1つ。)

C ++などの命令型プログラミング言語では、関数は数学の関数のように動作しません。たとえば、単一の浮動小数点引数を取り、浮動小数点の結果を返すC ++関数があるとします。表面的には、実数を実数にマッピングする数学関数のように見えるかもしれませんが、C ++関数は、引数に依存する数値を返すだけではありません。グローバル変数の値を読み書きしたり、画面に出力を書き込んだり、ユーザーからの入力を受け取ったりできます。ただし、純粋な関数型言語では、関数は引数で指定されたもののみを読み取ることができ、世界に影響を与えることができる唯一の方法は、返される値を使用することです。


9
…インターネットだけでなく、どこでも最良の方法。(以下の私の回答で述べた、Wadlerのオリジナルの関数型プログラミングのモナドも良いものです。)膨大な数のチュートリアルによるアナリシスによるチュートリアルは、どれにも近づきません。
ShreevatsaR 2011

13
このSigfpeの投稿のJavaScriptの翻訳は、高度なHaskellをまだ理解していない人にとって、モナドを学ぶための新しい最良の方法です。
Sam Watkins、2014年

1
これは私がモナドとは何かを学んだ方法です。多くの場合、概念を発明するプロセスを読者に説明することが、概念を教えるための最良の方法です。
ヨルダン

ただし、screenオブジェクトを引数として受け取り、テキストを変更してそのコピーを返す関数は純粋です。
Dmitri Zaitsev 2016年

87

モナドは>>=(aka bind)とreturn(aka unit)という2つの演算を持つデータ型です。return任意の値を取り、それを使ってモナドのインスタンスを作成します。>>=モナドのインスタンスを受け取り、その上に関数をマッピングします。(モナドは奇妙な種類のデータ型であることが既にわかります。ほとんどのプログラミング言語では、任意の値を取り、それから型を作成する関数を作成できなかったためです。モナドは一種のパラメトリック多態性を使用します。)

Haskell表記では、モナドインターフェースは

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

これらの操作は、特定の「法則」を従うことになって、それはterrifically重要ではありませんされています。「法律」は、単に操作の仕方賢明な実装では、基本的には(振る舞うべき体系化、ということ>>=とはreturn値がモナドのインスタンスとに変換し得るかについて合意するべきですそれ>>=は連想です)。

モナドは、状態とI / Oだけではありません。状態、I / O、例外、非決定性の操作を含む一般的な計算パターンを抽象化します。おそらく理解するのに最も簡単なモナドはリストとオプションタイプです:

instance Monad [ ] where
    []     >>= k = []
    (x:xs) >>= k = k x ++ (xs >>= k)
    return x     = [x]

instance Monad Maybe where
    Just x  >>= k = k x
    Nothing >>= k = Nothing
    return x      = Just x

ここで、[]および:リストのコンストラクタは、++連結演算子であり、JustそしてNothingあるMaybeコンストラクタ。これらのモナドはどちらも、それぞれのデータ型に関する一般的で有用な計算パターンをカプセル化しています(どちらも副作用やI / Oとは関係がないことに注意してください)。

モナドとは何か、なぜモナドが有用なのかを理解するために、重要なHaskellコードを書く必要があります。


「関数をその上にマッピングする」とはどういう意味ですか?
Casebash 2010年

導入部ではCasebash、わざと非公式にしています。「関数のマッピング」が何を伴うかを理解するために、終わり近くの例を参照してください。
Chris Conway

3
モナドはデータ型ではありません。これは関数を構成するルールです:stackoverflow.com/a/37345315/1614973
Dmitri Zaitsev

@DmitriZaitsevは正しい、モナドは実際に独自のデータデータ型、モナドはデータ型を提供しない
sksallaj

78

まず、ファンクタとは何かを理解する必要があります。その前に、高次関数を理解してください。

高次関数は、単に引数として関数を取る関数です。

ファンクタは、任意のタイプの構造であるT高次機能が存在するため、それを呼び出してmap、その変換タイプの機能はa -> b(任意の二種類を与えa及びb機能に)T a -> T b。このmap関数は、次の式がすべてpq(Haskell表記)に対してtrueを返すように、同一性と構成の法則にも従う必要があります。

map id = id
map (p . q) = map p . map q

たとえば、呼び出された型コンストラクタListは、(a -> b) -> List a -> List b上記の法則に従う型の関数を備えている場合、ファンクタです。唯一の実用的な実装は明らかです。結果のList a -> List b関数は、指定されたリストを反復処理し、(a -> b)各要素の関数を呼び出して、結果のリストを返します。

モナドは、本質的に単にファンクタであるT2つの追加方法と、join型のT (T a) -> T a、およびunit(と呼ばれることもあるreturnforkまたはpureタイプの)a -> T a。Haskellのリストの場合:

join :: [[a]] -> [a]
pure :: a -> [a]

なぜそれが役立つのですか?たとえば、mapリストを返す関数を使用してリストを上書きできるからです。Join結果のリストのリストを受け取り、それらを連結します。Listこれは可能だからモナドです。

あなたはない関数を書くことができmap、その後、join。この関数は、、、bindまたはflatMap、または(>>=)、またはと呼ばれ(=<<)ます。これは通常、Haskellでモナドインスタンスが与えられる方法です。

モナドは特定の法則を満たさjoinなければなりません。つまり、それは連想でなければなりません。あなたは価値がある場合、この手段xタイプのを[[[a]]]、その後join (join x)等しくなければなりませんjoin (map join x)。そしてpurejoinそのようなもののためのアイデンティティでなければなりませんjoin (pure x) == x


3
「高次関数」のdefへのわずかな追加:OR RETURN関数を取ることができます。だからこそ、彼らは自分自身で物事を行う「より高い」「仲間」なのです。
Kevin Won

9
その定義では、加算は高次関数です。数値を受け取り、その数値を別の数値に加算する関数を返します。したがって、いいえ、高次関数は厳密に関数で構成されるドメインを持つ関数です。
Apocalisp 2010年

ビデオ「ブライアン・ベックマン:モナドを恐れないでください」は、これと同じ論理の流れに従います。
icc97 2016年

48

[免責事項:私はまだモナドを完全に理解しようとしています。以下は、私がこれまでに理解したことです。それが間違っているなら、うまくいけば、知識のある誰かがカーペットの上で私を呼ぶでしょう。]

アーナーは書いた:

モナドは単に物をラップする方法であり、ラップされたものをアンラップせずに操作を行うメソッドを提供します。

それだけです。アイデアは次のようになります。

  1. ある種の価値を取り、それをいくつかの追加情報でラップします。値が特定の種類(整数や文字列など)であるように、追加情報も特定の種類です。

    たとえば、その追加情報はまたはである可能性がMaybeありIOます。

  2. 次に、ラップされたデータを操作しながら、その追加情報を処理できるいくつかの演算子があります。これらの演算子は、追加情報を使用して、ラップされた値に対する操作の動作を変更する方法を決定します。

    たとえば、a Maybe IntJust IntまたはNothingです。ここで、a Maybe Intをに追加するとMaybe Int、演算子は両方Just Intが内にあるかどうかを確認し、そうである場合は、Intsのラップを解除し、追加演算子を渡して、結果Intを新しいJust Int(これは有効です)に再ラップします。Maybe Int)、したがってを返しますMaybe Int。しかし、それらの1つがNothing内部にある場合、この演算子はただちに戻りNothingますMaybe Int。これも有効です。そうすることで、Maybe Intsが通常の数であるかのように見せかけ、通常の計算を実行できます。を取得する場合Nothingでも、方程式は正しい結果を生成しますNothingあらゆる場所でチェックをくずす必要はありません

しかし、その例はまさにのために何が起こるかですMaybe。追加情報がである場合、sにIO定義されたその特別な演算子IOが代わりに呼び出され、追加を実行する前にまったく異なる何かを実行できます。(OK、2つIO Intのsを一緒に追加することはおそらく無意味です–まだわかりません。)(また、Maybe例に注意を払った場合、「値を余分なものでラップする」ことが常に正しいとは限らないことに気付きました。しかし、それは難しいです。不可解なことなく正確、正確、正確であること。)

基本的に、「モナド」はおおまかに「パターン」を意味します。しかし、非公式に説明され、具体的に名前が付けられたパターンが満載の本の代わりに、新しい構文をプログラムの中で物事として宣言できるようにする言語構造(構文など)があります。(ここでの不正確さは、すべてのパターンが特定の形式に従う必要があるため、モナドはパターンほど一般的ではありません。しかし、ほとんどの人が知って理解している最も近い用語だと思います。)

そして、それが人々がモナドをとても混乱させる理由です:それらはとても一般的な概念だからです。何かをモナドにするものを尋ねるのは、何かをパターンにするものを尋ねるのと同様にあいまいです。

しかし、パターンのアイデアを言語で構文的にサポートすることの意味を考えてください。Gangof Fourの本を読んで特定のパターンの構成を覚える代わりに、不可知論者にこのパターンを実装するコードを書くだけです一度一般的な方法で、それで完了です!その後、ビジター、ストラテジー、ファサードなど、このパターンを再利用できます。コードで操作を装飾するだけで、何度も再実装する必要はありません。

だからこそ、モナドを理解している人々がモナドをとても便利だと思うのです。知的スノッブが理解に誇りを持っているのは象牙の塔の概念ではありませんが(もちろん、もちろんteehee)、実際にはコードが単純になります。


12
「学習者」(あなたのような)からの説明は、専門家からの説明よりも別の学習者に関連している場合があります。学習者は同じように考える:)
Adrian

何かをモナドにするのは、typeを持つ関数の存在ですM (M a) -> M a。それをタイプの1つに変えることができるという事実M a -> (a -> M b) -> M bは、それらを有用にするものです。
ジェレミーリスト

「モナド」はおおよそ「パターン」を意味します...いいえ。
ありがとう

44

多くの努力の結果、モナドがようやく理解できたと思います。圧倒的にトップ投票された回答についての私自身の長い批評をもう一度読んだ後、私はこの説明を提供します。

モナドを理解するために答える必要がある3つの質問があります:

  1. なぜモナドが必要なのですか?
  2. モナドとは何ですか?
  3. モナドはどのように実装されていますか?

私の元のコメントで述べたように、モナドの説明が多すぎて質問3に巻き込まれ、質問2や質問1を実際に適切にカバーする前に、その前に、そしてその前に。

なぜモナドが必要なのですか?

Haskellのような純粋な関数型言語は、CやJavaのような命令型言語とは異なり、純粋な関数型プログラムは特定の順序で一度に1ステップずつ実行されるとは限りません。Haskellプログラムは、数学関数に似ています。この関数では、任意の数の潜在的な順序で「方程式」を解くことができます。これにはいくつかの利点があります。その中には、特定の種類のバグ、特に「状態」などに関連するバグの可能性が排除されるという点があります。

ただし、このスタイルのプログラミングでは簡単に解決できない問題があります。コンソールプログラミングやファイルI / Oなど、特定の順序で発生する必要があるものや、状態を維持する必要があるものがあります。この問題に対処する1つの方法は、計算の状態を表す一種のオブジェクト、および状態オブジェクトを入力として受け取り、変更された新しい状態オブジェクトを返す一連の関数を作成することです。

それでは、コンソール画面の状態を表す架空の「状態」値を作成してみましょう。正確にこの値が構築される方法は重要ではありませんが、現在画面に表示されているものを表すバイト長のASCII文字の配列と、ユーザーが入力した最後の入力行を擬似コードで表す配列であるとします。コンソール状態を取得して変更し、新しいコンソール状態を返す関数をいくつか定義しました。

consolestate MyConsole = new consolestate;

したがって、コンソールプログラミングを行うには、純粋な関数型の方法で、相互に多くの関数呼び出しをネストする必要があります。

consolestate FinalConsole = print(input(print(myconsole, "Hello, what's your name?")),"hello, %inputbuffer%!");

この方法でプログラミングすると、「純粋な」機能スタイルが維持され、コンソールへの変更が特定の順序で行われます。ただし、上記の例のように、一度にいくつかの操作以上の操作を実行する必要があります。このように入れ子関数が不安定になり始めます。必要なのは、上記と基本的に同じことを行うコードですが、次のように書かれています。

consolestate FinalConsole = myconsole:
                            print("Hello, what's your name?"):
                            input():
                            print("hello, %inputbuffer%!");

これは確かにそれを書くためのより便利な方法でしょう。それをどうやってやるの?

モナドとは何ですか?

consolestate定義するタイプ(など)と、そのタイプで動作するように特別に設計された一連の関数を取得したら:、自動的に(バインド)のような演算子を定義することにより、これらのパッケージ全体を「モナド」に変換できます。フィードは、左側の戻り値を右側の関数パラメーターに、そしてlift通常の関数を特定の種類のバインド演算子で機能する関数に変換する演算子に渡します。

モナドはどのように実装されていますか?

他の回答を参照してください。その詳細に飛び込むのは自由です。


モナドを定義する理由はシーケンスだけではありません。モナドは、バインドとリターンを持つファンクタです。バインドして返すと、シーケンス処理が行われます。しかし、彼らは他のものも与えます。また、お気に入りの命令型言語は事実上、OOクラスを備えた豪華なIOモナドであることに注意してください。モナドの定義を簡単にすることは、インタープリターパターンを簡単に使用できることを意味します-dslをモナドとして定義し、解釈します!
nomen


38

数年前にこの質問に答えた後、私はその応答を改善して簡素化できると信じています...

モナドは、合成関数を使用して一部の入力シナリオの処理を外部化し、合成bind中に入力を前処理する関数合成手法です。

通常の合成では、関数はcompose (>>)、合成された関数を前の結果に順番に適用するために使用されます。重要なのは、合成される関数は、その入力のすべてのシナリオを処理する必要があることです。

(x -> y) >> (y -> z)

この設計は、関連する状態がより簡単に問い合わせられるように入力を再構成することによって改善できます。だから、代わりに単にy値がなることができMb、例えば、このような(is_OK, b)場合のy妥当性の概念が含まれています。

たとえば、入力が数字のみの場合、数字を含むかどうかにかかわらず、文字列を返す代わりboolに、有効な数字とのようなタプル内の数字の存在を示すに型を再構成できますbool * float。合成された関数は、数値が存在するかどうかを判断するために入力文字列を解析する必要がなくなりbool、タプルの部分を検査するだけで済みました。

(Ma -> Mb) >> (Mb -> Mc)

ここでも、合成はで自然に行われるcomposeため、各関数は入力のすべてのシナリオを個別に処理する必要がありますが、そうすることではるかに簡単になりました。

ただし、シナリオの処理が日常的であるような場合に、尋問の労力を外部化できるとしたらどうでしょう。例えば、どのような入力をするときのようにOKでないとき、私たちのプログラムは、何もしない場合is_OKですfalse。それが行われていれば、合成された関数はそのシナリオを自分で処理する必要がなく、コードが大幅に簡素化され、再利用のレベルが向上します。

この外部化を実現するbind (>>=)には、のcomposition代わりにを実行する関数を使用できますcompose。そのため、ある関数の出力から別の関数の入力に値を単純に転送する代わりにBind、のM部分を検査しMa、合成された関数をに適用するかどうか、および適用する方法を決定しますa。もちろん、この関数bindM、構造を検査して必要な種類のアプリケーションを実行できるように、特定のユーザー向けに具体的に定義されます。それでも、アプリケーションが必要であると判断したときに、検査されていない関数を合成関数に渡すだけなaので、は何でもかまいbindませaん。さらに、合成された関数自体が、M入力構造の一部も単純化します。したがって...

(a -> Mb) >>= (b -> Mc) より簡潔に Mb >>= (b -> Mc)

つまり、モナドは外部化し、入力が十分に公開されるように設計された後、特定の入力シナリオの処理に関する標準的な動作を提供します。この設計はshell and content、シェルが構成された関数のアプリケーションに関連するデータを含み、質問され、そのbind関数でのみ使用可能なモデルです。

したがって、モナドは次の3つです。

  1. Mモナド関連情報を保持するためのシェル、
  2. bindシェル内で見つかったコンテンツ値への合成関数の適用でこのシェル情報を利用するために実装された関数、および
  3. a -> Mbモナディック管理データを含む結果を生成するフォームの構成可能な関数。

一般的に言えば、関数への入力は、エラー条件などを含む可能性がある出力よりもはるかに制限されています。したがって、Mb結果の構造は一般的に非常に役立ちます。たとえば、除数がの場合、除算演算子は数値を返しません0

また、monadSは、ラップ値ことラップ機能を含んでいてもよいa、モナド型に、Ma、、および一般的な機能a -> b、単項関数に、a -> Mb適用後、その結果を包むことによって、。もちろん、同様にbind、そのようなラップ関数はに固有Mです。例:

let return a = [a]
let lift f a = return (f a)

bind関数の設計は、不変のデータ構造と純粋な関数を前提としており、他のものは複雑になり、保証はできません。そのため、モナドの法則があります。

与えられた...

M_ 
return = (a -> Ma)
f = (a -> Mb)
g = (b -> Mc)

その後...

Left Identity  : (return a) >>= f === f a
Right Identity : Ma >>= return    === Ma
Associative    : Ma >>= (f >>= g) === Ma >>= ((fun x -> f x) >>= g)

Associativitybindいつbind適用されたかに関係なく、評価の順序が保持されることを意味します。すなわち、の定義の中Associativity括弧の力、上記の早期評価bindingfgだけ期待する機能になりますMaためには完了することbind。したがって、の評価はMa、その値が適用される前に決定する必要がfあり、その結果はに適用されgます。


「しかし、他の人が役に立てば幸いです」強調されたすべての文にかかわらず、それは本当に私にとって有用でした:D

これは、私が今まで読んだり見たり聞いたりしたモナドの最も簡潔で明確な説明です。ありがとうございました!
Jamesが

モナドとモノイドの間には重要な違いがあります。モナドは間の「コン」機能にルールで異なる彼らはモノイドのために必要に応じて、詳細についてはこちらをご覧バイナリ操作を形成していないので、種類:stackoverflow.com/questions/2704652/...
ドミトリ・ザイツェフ

はい。あなたは正しいです。あなたの記事は私の頭の上にありました:)。しかし、私はこの治療法が非常に役立つと感じました(そして、他の人への指示として私の治療法にそれを加えました)。ご協力
George

2
あなたは代数的群論とモナドの出身地であるカテゴリー理論を混同しているかもしれません。前者は無関係な代数群の理論です。
Dmitri Zaitsev 2017年

37

モナドは、事実上、「型演算子」の一種です。3つのことを行います。最初に、あるタイプの値を別のタイプ(通常は「モナディックタイプ」と呼ばれます)に「ラップ」(または変換)します。第2に、モナディック型で使用できる基本型で使用できるすべての操作(または関数)を作成します。最後に、自身を別のモナドと組み合わせて複合モナドを生成するためのサポートを提供します。

「多分モナド」は、Visual Basic / C#の「null許容型」と本質的に同等です。nullを許容しない型 "T"を取り、それを "Nullable <T>"に変換してから、すべての2項演算子がNullable <T>で何を意味するかを定義します。

副作用は同じように表されます。関数の戻り値とともに副作用の説明を保持する構造が作成されます。次に、「リフトされた」操作は、関数間で値が渡されるときに副作用を回避します。

これらは、いくつかの理由により、「タイプ演算子」のわかりやすい名前ではなく、「モナド」と呼ばれています。

  1. モナドには、何ができるかについての制限があります(詳細については、定義を参照してください)。
  2. これらの制限は、3つの演算が含まれるという事実とともに、数学の曖昧な分野であるカテゴリー理論のモナドと呼ばれるものの構造に準拠しています。
  3. それらは「純粋な」関数型言語の支持者によって設計されました
  4. 数学の曖昧な分岐のような純粋な関数型言語の支持者
  5. 数学は曖昧であり、モナドはプログラミングの特定のスタイルに関連付けられているため、人々はモナドという単語を一種の秘密の握手として使用する傾向があります。このため、より良い名前に投資することに誰も悩まされていません。

1
モナドは「設計」されておらず、あるドメイン(カテゴリ理論)から別のドメイン(純粋に関数型プログラミング言語のI / O)に適用されました。ニュートンは計算を「設計」しましたか?
Jared Updike、

1
上記のポイント1と2は正しく、有用です。ポイント4と5は、多かれ少なかれ真実であっても、一種のアドホミネムです。それらはモナドを説明する助けにはなりません。
Jared Updike、

13
再:4、5:「秘密の握手」の事は赤いニシンです。プログラミングは専門用語でいっぱいです。Haskellは、何かを再発見するふりをすることなく、たまたまそれを何かと呼んでいます。それがすでに数学に存在しているなら、なぜそれを新しい名前にするのですか?名前は本当に人々がモナドを取得しない理由ではありません。それらは微妙な概念です。平均的な人はおそらく加法と乗法を理解しているでしょう。なぜ彼らはアーベル群の概念を理解しないのでしょうか?それはより抽象的で一般的であり、その人は概念に頭を包む仕事をしていないからです。名前の変更は役に立ちません。
Jared Updike、

16
ため息...私はハスケルを攻撃していません...私は冗談を言っていました。だから、私は「アドホミネム」であることについて少しは理解していません。はい、計算は「設計されました」。そのため、たとえば、微積分学の学生は、Netwtonが使用する厄介なものではなく、ライプニッツの表記法を教えられます。より良いデザイン。良い名前はたくさんの理解に役立ちます。Abelian Groupsを「膨張したしわの鞘」と呼んだ場合、あなたは私を理解するのに苦労するかもしれません。あなたは「しかしその名前はナンセンスだ」と言っているかもしれませんが、誰もそれをそれらと呼ぶことは決してありません。カテゴリー理論「モナド」を聞いたことがない人にとっては、ナンセンスのように聞こえます。
Scott Wisniewski、

4
@スコット:私の広範なコメントでHaskellについて防御的になっていたように思われた場合は申し訳ありません。私は秘密の握手についてのあなたのユーモアを楽しんでいます、そしてあなたは私がそれが多かれ少なかれ本当であると言ったことに気付くでしょう。:-) Abelian Groupsを「膨張したしわのポッド」と呼んだ場合、モナドに「より良い名前」を付けるのと同じ間違いを犯すことになります(F#の「計算式」を参照)。ありますが、「暖かいファジィなもの」(または「計算式」)とは異なります。「型演算子」という用語の使い方を正しく理解していれば、モナド以外にも多くの型演算子があります。
Jared Updike、

35

モナドとは?の回答も参照してください

モナドへの良い動機はsigfpe(Dan Piponi)のあなたがモナドを発明したかもしれません!(そして多分あなたはすでに持っています)。他にもたくさんのモナドチュートリアルがありますが、その多くは誤ってモナドをさまざまな類推を使って「簡単な用語」で説明しようとしています。これはモナドチュートリアルの誤りです。それらを避けてください。

マカイバー博士が言っているように、あなたの言語が悪い理由教えてください

だから、Haskellについて嫌いなこと:

明白から始めましょう。モナドのチュートリアル。いいえ、モナドではありません。特にチュートリアル。彼らは無限で、誇張され、親愛なる神です。さらに、それらが実際に役立つという説得力のある証拠を見たことがありません。クラス定義を読み、コードを書き、恐ろしい名前を乗り越えてください。

Maybeモナドを理解していると?いいでしょう、あなたは途中です。他のモナドの使用を開始するだけで、遅かれ早かれ、一般的なモナドが何であるかを理解できます。

[数学を志向している場合は、数十のチュートリアルを無視して定義を学ぶか、カテゴリ理論の講義に従うことをお勧めします。)定義の主な部分は、モナドMがそれぞれについて定義する「型コンストラクタ」を含むことです既存のタイプ「T」、新しいタイプ「MT」、および「通常の」タイプと「M」タイプの間を行き来するためのいくつかの方法。]

また、驚くべきことに、モナドへの最良の紹介の1つは、実際にはモナドを紹介する初期の学術論文の1つである関数型プログラミングのためのフィリップワドラーのモナドです。実際の人工チュートリアルの多くとは異なり、実際には実用的で重要な動機付けの例があります。


2
ワドラーの論文の唯一の問題は表記が異なることですが、私はその論文がかなり説得力があり、モナドを適用する明確な簡潔な動機であることに同意します。
Jared Updike

「モナドチュートリアルの誤り」の+1。モナドのチュートリアルは、整数の概念を説明しようとするいくつかのチュートリアルを持つことに似ています。1つのチュートリアルは、「1はリンゴに似ている」と言うでしょう。別のチュートリアルでは、「2は梨のようなものです」とあります。3番目は、「3は基本的にオレンジです」と言います。ただし、単一のチュートリアルから全体像を取得することはできません。私がそこから得たのは、モナドは多くのまったく異なる目的に使用できる抽象的な概念であるということです。
stakx-2011年

@stakx:はい、そうです。しかし、私はモナドがあなたが学ぶことができないか、または学ぶべきではない抽象であることを意味しませんでした。基礎となる単一の抽象化を理解するのに十分な具体例を見た後でそれを学ぶのが最善であるということだけです。私の他の答えをここでください。
ShreevatsaR 2011年

5
時々、複雑で有用なことをするコードを使用することによってモナドが有用であることを読者に納得させようとする非常に多くのチュートリアルがあると感じます。それは何ヶ月もの間私の理解を妨げました。私はそのように学びません。私は非常に単純なコードを見るのが好きです。精神的に通過できる愚かなことをしていて、この種の例を見つけることができませんでした。最初の例が複雑な文法を解析するモナドかどうかはわかりません。整数を合計するモナドかどうかを知ることができます。
Rafael S. Calsaverini、2011年

タイプコンストラクタのみの記述は不完全です:stackoverflow.com/a/37345315/1614973
Dmitri Zaitsev

23

モナドは、データに対する抽象データ型とは何かをフローを制御するためのものです。

つまり、多くの開発者は、セット、リスト、辞書(またはハッシュ、マップ)、およびツリーの概念に慣れています。これらのデータ型には、多くの特殊なケースがあります(たとえば、InsertionOrderPreservingIdentityHashMap)。

ただし、プログラムの「フロー」に直面すると、多くの開発者は、if、switch / case、do、while、goto(grr)、および(たぶん)クロージャーよりもはるかに多くの構造にさらされていません。

したがって、モナドは単に制御フロー構造です。モナドを置き換えるより良いフレーズは「コントロールタイプ」でしょう。

そのため、モナドには制御ロジック、ステートメント、または関数用のスロットがあります。データ構造で同等のものとは、一部のデータ構造ではデータを追加および削除できるということです。

たとえば、「if」モナド:

if( clause ) then block

最も単純なのは、2つのスロット(節とブロック)です。ifモナドは通常、節の結果を評価するために構築され、偽でない場合は、ブロックを評価しています。多くの開発者は、「もし」を学ぶときにモナドを紹介されていません。効果的なロジックを書くためにモナドを理解する必要はありません。

モナドは、データ構造がより複雑になるのと同じように、より複雑になる可能性がありますが、モナドには、セマンティクスが似ているが実装や構文が異なる可能性のある幅広いカテゴリが多数あります。

もちろん、データ構造を反復したり、トラバースしたりするのと同じ方法で、モナドを評価できます。

コンパイラは、ユーザー定義のモナドをサポートしている場合とサポートしていない場合があります。Haskellは確かにそうです。Iokeにはいくつかの同様の機能がありますが、モナドという用語はこの言語では使用されていません。


14

私のお気に入りのモナドチュートリアル:

http://www.haskell.org/haskellwiki/All_About_Monads

(「モナドチュートリアル」のGoogle検索で170,000ヒットのうち)

@Stu:モナドのポイントは、(通常は)順次セマンティクスを別の方法で純粋なコードに追加できるようにすることです。モナドを作成して(モナド変換を使用して)、エラー処理、共有状態、ロギングなどの解析など、より複雑で複雑なセマンティクスを取得することもできます。これはすべて純粋なコードで可能であり、モナドはそれを抽象化してモジュラーライブラリで再利用するだけで(常にプログラミングに優れています)、便利な構文を提供して命令型にすることができます。

Haskellにはすでに演算子のオーバーロードがあります[1]。JavaまたはC#でインターフェイスを使用するのと同じように型クラスを使用しますが、Haskellはたまたま、+ &&や>などの非英数字トークンをインフィックス識別子として許可します。「セミコロンのオーバーロード」[2]を意味する場合は、演算子のオーバーロードのみです。まるで魔法のように聞こえ、「セミコロンをオーバーロード」する問題を求めるように聞こえます(Perlのハッカーがこのアイデアに気づき始める画像)。モナドなければ、純粋に機能的なコードは明示的なシーケンスを必要とせず、許可もしないので、セミコロンはありません。

これはすべて、必要以上に複雑に聞こえます。sigfpeの記事はかなりクールですが、Haskellを使用して説明しています。これは、Haskellを理解してモナドを理解し、Monadsを理解してモナドを理解するという鶏と卵の問題を解決するのに失敗しています。

[1]これはモナドとは別の問題ですが、モナドはHaskellの演算子オーバーロード機能を使用しています。

[2]モナディックアクションをチェーンするための演算子は>> =(「バインド」と発音)であるため、これは単純化しすぎですが、中括弧、セミコロン、インデント、および改行を使用できる構文糖(「do」)があります。


9

最近、モナドを別の方法で考えています。私は、それらを実行順序を数学的に抽象化し、新しい種類の多態性を可能にするものと考えてきました。

命令型言語を使用していて、いくつかの式を順番に記述した場合、コードは常にその順序で正確に実行されます。

そして、単純なケースでは、モナドを使用するときも同じように感じられます-順番に発生する式のリストを定義します。ただし、使用するモナドによっては、コードが順番に(IOモナドのように)順番に実行され、(Listモナドのように)一度に複数のアイテムで並列に実行され、途中で停止する場合があります(Maybeモナドのように) 、途中で一時停止して後で再開する(再開モナドのように)、巻き戻して最初から開始する(トランザクションモナドのように)、または途中で巻き戻して他のオプションを試す(ロジックモナドのように) 。

また、モナドはポリモーフィックであるため、ニーズに応じて、異なるモナドで同じコードを実行することが可能です。

さらに、場合によっては、モナドを(モナド変換子と)組み合わせて、同時に複数の機能を取得することができます。


9

私はまだモナドは初めてですが、読んで本当に良かったと感じたリンクを共有したいと思いました(写真付き!!):http : //www.matusiak.eu/numerodix/blog/2012/3/11/素人向けモナド/ (所属なし)

基本的に、私が記事から得た温かくファジーなコンセプトは、モナドは基本的に、さまざまな関数を構成可能な方法で機能させるアダプターです。つまり、複数の関数をつなぎ合わせて、一貫性のない戻りを心配することなくそれらを組み合わせることができます。タイプなど。したがって、BIND関数は、これらのアダプターを作成するときに、リンゴとリンゴを、オレンジとオレンジを保つ役割を果たします。また、LIFT関数は、「下位レベル」の関数を取り、それらを「アップグレード」してBIND関数と連携し、同様に構成できるようにします。

私はそれが正しく理解されたことを願っています、そしてさらに重要なことに、記事がモナドについて有効な見解を持っていることを願っています。他に何もなければ、この記事はモナドについてもっと学ぶための私の食欲を刺激するのに役立ちました。


pythonの例は理解しやすくしました!共有いただきありがとうございます。
ライアンエフェンディ

8

上記の優れた回答に加えて、モナドをJavaScriptライブラリjQuery(および「メソッドチェーン」を使用してDOMを操作する方法)に関連付けることによってモナドを説明する(Patrick Thomsonによる)次の記事へのリンクを提供します。: jQueryはモナドです

jQueryのドキュメント自体は、用語「モナド」を参照してください。おそらく、より馴染みのある「ビルダーパターン」について協議しません。これは、気づかないうちに適切なモナドがあるという事実を変更するものではありません。


jQueryを使用している場合、特にHaskellが強力でない場合、この説明は非常に役立ちます
byteclub

10
jQueryはモナドではありません。リンクされた記事は間違っています。
トニーモリス

1
「強調する」ことはあまり説得力がありません。このトピックに関するいくつかの有用なディスカッションについては、jQueryはモナドですか?-スタックオーバーフロー
nealmcb 2013年

1
参照してくださいダグラスCrackfordのGoogleトークモナドと生殖腺およびAJAXライブラリと約束の類似の挙動に拡大modadsを行うための彼のJavascriptコード、:ダグラス・クロックフォード/モナド・GitHubの
nealmcb


7

モナドは、共通のコンテキストを共有する計算を組み合わせる方法です。パイプのネットワークを構築するようなものです。ネットワークを構築するとき、そこを流れるデータはありません。しかし、すべてのビットを「バインド」と「リターン」でつなぎ合わせた後、次のようなものを呼び出しrunMyMonad monad data、データがパイプを流れます。


1
それはモナドよりもむしろアプリケーションのようなものです。モナドでは、接続する次のパイプを選択する前に、パイプからデータを取得する必要があります。
ピーカー、2010

はい、あなたはモナドではなくアプリケーションについて説明します。モナドは、そのポイントに到達したデータに応じて、パイプ内でその場で次のパイプセグメントを構築します。
ネスは


5

私が正しく理解していれば、IEnumerableはモナドから派生しています。それは、C#の世界の私たちにとって興味深いアプローチの角度かもしれませんか?

価値のあるものとして、私を助けてくれたチュートリアルへのリンクをいくつか紹介します(いいえ、モナドとはまだ理解していません)。


5

そこについて学ぶときに私を最もよく助けた2つのことは:

Graham Huttonの著書 『Programming in Haskell』の第8章「Functional Parsers」。これは実際にはモナドについてはまったく触れていませんが、章を読み進めてその中のすべて、特にバインド操作のシーケンスがどのように評価されるかを本当に理解できれば、モナドの内部について理解できます。これには数回の試行が必要です。

チュートリアルAll About Monads。これはそれらの使用のいくつかの良い例を与えており、私が私のために働いたアペンデックスの類推を言わなければなりません。


5

モノイドは、モノイドとサポートされている型で定義されたすべての操作が常にモノイド内でサポートされている型を返すことを保証するもののようです。たとえば、任意の数+任意の数=数、エラーなし。

一方、除算は2つの小数を受け入れ、小数を返します。これは、ゼロによる除算をhaskell somewhy(たまたま小数のsomewhy)の無限大として定義しました...

いずれにせよ、モナドは操作のチェーンが予測可能な方法で動作することを保証するための単なる方法であり、Num-> Numであると主張する関数は、xで呼び出されたNum-> Numの別の関数で構成されていません言う、ミサイルを発射します。

一方、ミサイルを発射する関数がある場合は、ミサイルを発射したいが、ミサイルを発射する他の関数と組み合わせて、ミサイルを発射することができます。なんらかの理由で「Hello World」を印刷しています。

Haskellでは、mainのタイプはIO()またはIO [()]であり、違いは奇妙であり、これについては説明しませんが、ここで私が考えていることは次のとおりです。

メインがある場合は、一連のアクションを実行したいのですが、プログラムを実行する理由は、通常はIOですが、効果を生み出すためです。したがって、私はIO操作をメインで一緒にチェーンして-IOを行うだけで、他には何もできません。

「IOを返さない」ことをやろうとすると、プログラムはチェーンが流れない、または基本的に「これが私たちがやろうとしていること、つまりIOアクションにどのように関係するのか」と文句を言うでしょう。プログラマーは、並べ替えのアルゴリズムを作成する一方で、ミサイルの発射について迷ったり、考えたりすることなく、思考の流れを維持します。

基本的に、モナドはコンパイラへのヒントのように見えます。「ねえ、ここで数値を返すこの関数は知っています。実際に機能するわけではなく、数値を生成することもあれば、まったく何もしないこともあります。マインド"。これを知っていて、モナディックアクションをアサートしようとすると、モナディックアクションはコンパイル時の例外として機能し、「これは実際には数値ではありません。これは数値である可能性がありますが、これを想定することはできません。流れが許容範囲内であることを確認してください。」これにより、予測できないプログラムの動作が防止されます。

モナドは純粋さや制御ではなく、すべての動作が予測可能で定義されている、またはコンパイルされないカテゴリのIDを維持することに関するものであるように見えます。何かをすることが期待されているときは何もできず、何もしない(目に見える)ことが期待されている場合は何もできない。

モナドについて私が考えることができる最大の理由は-手続き型/ OOPコードを見ると、プログラムの開始点も終了点もわからないことがわかります。 、魔法、そしてミサイル。このコンテキストのモジュール性は相互に依存する「セクション」に基づいているため、それを維持できなくなり、可能であれば、プログラムの一部を理解する前にプログラム全体にかなりの時間を費やすことになります。コードの効率性と相互関係を約束するために、コードができるだけ関連するように最適化されています。モナドは非常に具体的であり、定義によって明確に定義されており、プログラムのフローが分析可能であり、分析が困難な部分を分離できるようにします。モナドは「または、宇宙を破壊したり、時間を歪めたりすることもできます。モナドはそれが何であるかを保証します。これは非常に強力です。または、宇宙を破壊したり、時間を歪めたりすることもできます。モナドはそれが何であるかを保証します。これは非常に強力です。

「現実世界」のすべてのものは、混乱を防ぐ明確な観察可能な法則に拘束されるという意味で、モナドであるように見えます。これは、クラスを作成するためにこのオブジェクトのすべての操作を模倣する必要があることを意味するのではなく、単に「正方形は正方形」、正方形のみ、長方形でも円でも、「正方形には面積既存の次元の長さのそれ自体を掛けたものです。どんな正方形を持っているとしても、2Dスペースの正方形の場合、その領域は絶対に長さの2乗以外には絶対にありえません。これは証明するのがほとんど自明です。私たちは、私たちの世界が現在の状態であることを確認するためにアサーションを作成する必要はありません。プログラムが軌道から外れるのを防ぐために、現実の影響を使用します。

間違いは間違いなく保証されていますが、これはそこにいる誰かを助けることができると思うので、うまくいけば誰かを助けることができます。


5

Scalaのコンテキストでは、以下が最も単純な定義であることがわかります。基本的に、flatMap(またはbind)は「連想」であり、アイデンティティが存在します。

trait M[+A] {
  def flatMap[B](f: A => M[B]): M[B] // AKA bind

  // Pseudo Meta Code
  def isValidMonad: Boolean = {
    // for every parameter the following holds
    def isAssociativeOn[X, Y, Z](x: M[X], f: X => M[Y], g: Y => M[Z]): Boolean =
      x.flatMap(f).flatMap(g) == x.flatMap(f(_).flatMap(g))

    // for every parameter X and x, there exists an id
    // such that the following holds
    def isAnIdentity[X](x: M[X], id: X => M[X]): Boolean =
      x.flatMap(id) == x
  }
}

例えば

// These could be any functions
val f: Int => Option[String] = number => if (number == 7) Some("hello") else None
val g: String => Option[Double] = string => Some(3.14)

// Observe these are identical. Since Option is a Monad 
// they will always be identical no matter what the functions are
scala> Some(7).flatMap(f).flatMap(g)
res211: Option[Double] = Some(3.14)

scala> Some(7).flatMap(f(_).flatMap(g))
res212: Option[Double] = Some(3.14)


// As Option is a Monad, there exists an identity:
val id: Int => Option[Int] = x => Some(x)

// Observe these are identical
scala> Some(7).flatMap(id)
res213: Option[Int] = Some(7)

scala> Some(7)
res214: Some[Int] = Some(7)

NOTE厳密に定義圏の関数型プログラミングにおけるモナドは、の定義と同じではありません圏論におけるモナドのターンで定義されている、mapflatten。それらは特定のマッピングの下で​​は一種の同等ですが。このプレゼンテーションは非常に優れています。http//www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category


5

この答えは、やる気を起こさせる例から始まり、例を通して働き、モナドの例を導き出し、正式に「モナド」を定義します。

疑似コードで次の3つの関数を検討してください。

f(<x, messages>) := <x, messages "called f. ">
g(<x, messages>) := <x, messages "called g. ">
wrap(x)          := <x, "">

fフォームの順序付きペアを取り、順序付きペアを<x, messages>返します。最初のアイテムはそのままで"called f. "、2番目のアイテムに追加されます。と同じgです。

これらの関数を作成して、関数が呼び出された順序を示す文字列とともに、元の値を取得できます。

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<x, "called g. ">)
= <x, "called g. called f. ">

あなたは、という事実を嫌うfし、g以前のログ情報を、独自のログメッセージを追加する責任があります。(ただ、議論のために想像し、その代わりに文字列を追加し、fそしてgペアの2番目の項目に複雑なロジックを実行しなければならないことは、繰り返しに苦痛であろうと、複雑なロジック2で- 。以上- 。さまざまな機能)

あなたはより単純な関数を書くことを好みます:

f(x)    := <x, "called f. ">
g(x)    := <x, "called g. ">
wrap(x) := <x, "">

しかし、それらを作成するとどうなるか見てください。

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<<x, "">, "called g. ">)
= <<<x, "">, "called g. ">, "called f. ">

問題は、ということである渡す関数にペアが何をしたいあなたを与えるものではありません。しかし、もしあなたがペアを関数に与えることができたらどうなるでしょう:

  feed(f, feed(g, wrap(x)))
= feed(f, feed(g, <x, "">))
= feed(f, <x, "called g. ">)
= <x, "called g. called f. ">

読むfeed(f, m)「飼料としてmの中へf」。するにはフィードのペアを<x, messages>関数にすることはfしている渡す xf、取得<y, message>のうちf、と返します<y, messages message>

feed(f, <x, messages>) := let <y, message> = f(x)
                          in  <y, messages message>

関数で次の3つのことを行うとどうなるかに注意してください。

最初に、値をラップしてから、結果のペアを関数にフィードする場合:

  feed(f, wrap(x))
= feed(f, <x, "">)
= let <y, message> = f(x)
  in  <y, "" message>
= let <y, message> = <x, "called f. ">
  in  <y, "" message>
= <x, "" "called f. ">
= <x, "called f. ">
= f(x)

これは、値を関数に渡すことと同じです。

第二:あなたがにペアをフィードする場合wrap

  feed(wrap, <x, messages>)
= let <y, message> = wrap(x)
  in  <y, messages message>
= let <y, message> = <x, "">
  in  <y, messages message>
= <x, messages "">
= <x, messages>

それはペアを変更しません。

第三:あなたがとる関数定義した場合xやフィードg(x)にはf

h(x) := feed(f, g(x))

それにペアをフィードします:

  feed(h, <x, messages>)
= let <y, message> = h(x)
  in  <y, messages message>
= let <y, message> = feed(f, g(x))
  in  <y, messages message>
= let <y, message> = feed(f, <x, "called g. ">)
  in  <y, messages message>
= let <y, message> = let <z, msg> = f(x)
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = let <z, msg> = <x, "called f. ">
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = <x, "called g. " "called f. ">
  in <y, messages message>
= <x, messages "called g. " "called f. ">
= feed(f, <x, messages "called g. ">)
= feed(f, feed(g, <x, messages>))

これは、ペアをgにフィードし、結果のペアをにフィードするのと同じfです。

あなたはモナドのほとんどを持っています。プログラムのデータ型について知る必要があるだけです。

値のタイプは何<x, "called f. ">ですか?まあ、それは値のタイプが何であるかに依存しますx。場合xタイプのものでありt、そして、あなたのペアは、タイプ「のペアの値でありt、文字列は」。そのタイプを呼び出しM tます。

M型コンストラクタです。M単独では型を参照しませんM _が、空白を型で埋めると型を参照します。アンはM intint型と文字列のペアです。アンは、M string文字列と文字列のペアです。等。

おめでとうございます、モナドを作成しました!

正式には、モナドはタプル<M, feed, wrap>です。

モナドは次のタプル<M, feed, wrap>です:

  • M 型コンストラクタです。
  • feed(とる関数取りt及び戻りM u)とM t戻りますM u
  • wrapを取り、vを返しますM v

tu、およびv、または同じであってもなくてもよい任意の3つのタイプがあります。モナドは、特定のモナドについて証明した3つのプロパティを満たします。

  • 摂食包まれたt関数にすることは同じである渡す開封さをt関数に。

    正式に: feed(f, wrap(x)) = f(x)

  • にをフィードM twrapても、には何も起こりませんM t

    正式に: feed(wrap, m) = m

  • 摂食M t(それを呼び出すmことを関数に)

    • 合格tへのg
    • からM u(それを呼び出すn)を取得しますg
    • にフィードnするf

    と同じです

    • mg
    • nから得るg
    • nf

    正式:feed(h, m) = feed(f, feed(g, m))どこh(x) := feed(f, g(x))

通常、feedbind>>=HaskellのAKA )とwrap呼ばれ、と呼ばれreturnます。


5

MonadHaskellのコンテキストで説明しようと思います。

関数型プログラミングでは、関数の構成が重要です。これにより、プログラムを小さくて読みやすい関数で構成できます。

2つの関数があるg :: Int -> Stringとしf :: String -> Boolます:と。

我々は行うことができます(f . g) xとちょうど同じである、f (g x)ところ、xあるInt値。

ある関数の結果を別の関数に合成/適用する場合、型を一致させることが重要です。上記の場合、が返す結果のタイプは、がg受け入れるタイプと同じでなければなりませんf

ただし、値がコンテキストに含まれている場合があります。これにより、型の整列が少し難しくなります。(コンテキストに値があると非常に便利です。たとえば、Maybe Int型はInt存在しない可能性がIO StringあるString値を表し、型はいくつかの副作用を実行した結果として存在する値を表します。)

今、g1 :: Int -> Maybe Stringとがあるとしf1 :: String -> Maybe Boolます。g1およびとそれぞれf1非常に似ています。gf

(f1 . g1) xor f1 (g1 x)はできません。xInt値です。が返す結果のタイプは、予期したg1ものでf1はありません。

私たちは、構成することができfgして.オペレータが、今私たちは、作曲することができないf1g1して.。問題は、コンテキストにない値を期待する関数に、コンテキスト内の値を直接渡すことができないことです。

作成できるようにg1and f1に演算子を導入すると便利だと(f1 OPERATOR g1) x思いませんか?g1コンテキストの値を返します。値はコンテキストから取り出され、に適用されf1ます。はい、そのような演算子があります。です<=<

>>=構文は少し異なりますが、まったく同じことを行う演算子もあります。

書きます:g1 x >>= f1g1 xあるMaybe Int値。>>=オペレータは、その取るのに役立ちますInt「おそらく、ない-そこに」コンテキストのうちの値を、そしてそれを適用しますf1。の結果でf1あるは、操作Maybe Bool全体の結果になります>>=

そして最後に、なぜMonad有用なのですか?Monad>>=演算子を定義する型クラスなのでEq==and /=演算子を定義する型クラスとほとんど同じです。

結論として、Monad型クラスは、>>=コンテキスト内の値を期待しない関数にコンテキスト内の値(これらのモナド値と呼ぶ)を渡すことができる演算子を定義します。コンテキストが処理されます。

ここで覚えておくべきことが1つあります。それは、Monadがコンテキストの値を含む関数の合成を許可するということです



IOW、モナドは一般化された関数呼び出しプロトコルです。
Will Ness

あなたの答えは私の意見では最も役に立ちます。私が強調しなければならないのは、参照している関数はコンテキストの値を含むだけでなく、コンテキストに積極的に値を入れるという事実に重点を置く必要があると私は言わなければならないことです。したがって、たとえば、関数f :: ma-> mbは、別の関数g :: mb-> m cと非常に簡単に構成されます。しかし、モナド(特にバインド)を使用すると、同じコンテキストで入力を行う関数を永続的に構成できます。最初にそのコンテキストから値を取り出す必要はありません(値から情報を効果的に削除します)
James

@ジェームス私はそれがファンクターのための強調であるべきだと思いますか?
ジョナス

@ジョナス私はきちんと説明しなかったと思います。関数がコンテキストに値を置くと言うとき、それらは型(a-> mb)を持っていることを意味します。コンテキストに値を入力すると新しい情報が追加されるため、これらは非常に便利ですが、a(a-> mb)とa(b-> mc)をチェーンすることは、値を取り出すことができないため、通常は困難です。コンテキストの。したがって、複雑なプロセスを使用して、特定のコンテキストに応じてこれらの関数を賢明な方法でチェーン化する必要があります。モナドでは、これをコンテキストに関係なく一貫した方法で実行できます。
ジェームズ

5

tl; dr

{-# LANGUAGE InstanceSigs #-}

newtype Id t = Id t

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

プロローグ

$関数のアプリケーションオペレーター

forall a b. a -> b

正規に定義されている

($) :: (a -> b) -> a -> b
f $ x = f x

infixr 0 $

Haskellプリミティブ関数アプリケーションの観点からf xinfixl 10)。

組成物.に関して定義される$ような

(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \ x -> f $ g x

infixr 9 .

同等性を満たす forall f g h.

     f . id  =  f            :: c -> d   Right identity
     id . g  =  g            :: b -> c   Left identity
(f . g) . h  =  f . (g . h)  :: a -> d   Associativity

.連想的でありid、その右と左のアイデンティティです。

クライスリトリプル

プログラミングでは、モナドはモナド型クラスのインスタンスを持つファンクタ型コンストラクタです。定義と実装にはいくつかの同等のバリアントがあり、それぞれモナドの抽象化についてわずかに異なる直感があります。

ファンクターは、ファンクター型クラスのインスタンスを持つf種類の型コンストラクターです* -> *

{-# LANGUAGE KindSignatures #-}

class Functor (f :: * -> *) where
   map :: (a -> b) -> (f a -> f b)

静的に強制される型プロトコルに従うことに加えて、ファンクター型クラスのインスタンスは代数ファンクター則に従う必要があります forall f g.

       map id  =  id           :: f t -> f t   Identity
map f . map g  =  map (f . g)  :: f a -> f c   Composition / short cut fusion

ファンクターの計算には次のタイプがあります

forall f t. Functor f => f t

計算c rは、コンテキスト内の結果 rで構成されます c

単項モナド関数またはKleisli矢印のタイプは

forall m a b. Functor m => a -> m b

クライシ矢印は、1つの引数を取りa、モナド計算を返す関数ですm b

モナドはクライスリトリプルによって正規に定義されます forall m. Functor m =>

(m, return, (=<<))

型クラスとして実装

class Functor m => Monad m where
   return :: t -> m t
   (=<<)  :: (a -> m b) -> m a -> m b

infixr 1 =<<

Kleisliのアイデンティティは return値を促進するKleisli矢印でtモナドコンテキストにm拡張機能またはクライスリアプリケーション =<<a -> m b、計算の結果にクライスリ矢印を適用しますm a

クライスリ構成 <=<は、拡張の観点から

(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
f <=< g = \ x -> f =<< g x

infixr 1 <=<

<=< 2つのKleisli矢印を作成し、左矢印を右矢印の適用結果に適用します。

モナドタイプクラスのインスタンスは、モナドの法則に従う必要があります。forall f g h.

   f <=< return  =  f                :: c -> m d   Right identity
   return <=< g  =  g                :: b -> m c   Left identity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d   Associativity

<=<連想的でありreturn、その右と左のアイデンティティです。

身元

IDタイプ

type Id t = t

型の恒等関数です

Id :: * -> *

ファンクタとして解釈され、

   return :: t -> Id t
=      id :: t ->    t

    (=<<) :: (a -> Id b) -> Id a -> Id b
=     ($) :: (a ->    b) ->    a ->    b

    (<=<) :: (b -> Id c) -> (a -> Id b) -> (a -> Id c)
=     (.) :: (b ->    c) -> (a ->    b) -> (a ->    c)

正規のHaskellでは、恒等モナドが定義されています

newtype Id t = Id t

instance Functor Id where
   map :: (a -> b) -> Id a -> Id b
   map f (Id x) = Id (f x)

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

オプション

オプションタイプ

data Maybe t = Nothing | Just t

Maybe t結果を必ずしも生成しないt計算、つまり「失敗」する可能性のある計算をエンコードします。オプションモナドが定義されています

instance Functor Maybe where
   map :: (a -> b) -> (Maybe a -> Maybe b)
   map f (Just x) = Just (f x)
   map _ Nothing  = Nothing

instance Monad Maybe where
   return :: t -> Maybe t
   return = Just

   (=<<) :: (a -> Maybe b) -> Maybe a -> Maybe b
   f =<< (Just x) = f x
   _ =<< Nothing  = Nothing

a -> Maybe b結果がMaybe a得られる場合にのみ、結果に適用されます。

newtype Nat = Nat Int

自然数は、ゼロ以上の整数としてエンコードできます。

toNat :: Int -> Maybe Nat
toNat i | i >= 0    = Just (Nat i)
        | otherwise = Nothing

自然数は減算では閉じていません。

(-?) :: Nat -> Nat -> Maybe Nat
(Nat n) -? (Nat m) = toNat (n - m)

infixl 6 -?

オプションモナドは、例外処理の基本的な形式をカバーしています。

(-? 20) <=< toNat :: Int -> Maybe Nat

リスト

リスト型を超えるリストモナド

data [] t = [] | t : [t]

infixr 5 :

そしてその追加モノイド操作は「追加」します

(++) :: [t] -> [t] -> [t]
(x : xs) ++ ys = x : xs ++ ys
[]       ++ ys = ys

infixr 5 ++

自然な量の結果が得られる非線形計算[t]をエンコードし0, 1, ...ますt

instance Functor [] where
   map :: (a -> b) -> ([a] -> [b])
   map f (x : xs) = f x : map f xs
   map _ []       = []

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> [a] -> [b]
   f =<< (x : xs) = f x ++ (f =<< xs)
   _ =<< []       = []

拡張機能は、要素へのKleisli矢印の適用から生じるすべてのリストを単一の結果リストに=<<連結++します。[b]f xa -> [b][a][b]

正の整数の適切な除数をしてみましょうnこと

divisors :: Integral t => t -> [t]
divisors n = filter (`divides` n) [2 .. n - 1]

divides :: Integral t => t -> t -> Bool
(`divides` n) = (== 0) . (n `rem`)

その後

forall n.  let { f = f <=< divisors } in f n   =   []

拡張の代わりにモナド型クラスを定義する=<<場合、Haskell標準はそのフリップであるバインド演算子を使用し>>=ます。

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

   (>>) :: forall a b. m a -> m b -> m b
   m >> k = m >>= \ _ -> k
   {-# INLINE (>>) #-}

   return :: a -> m a
   return = pure

簡単にするために、この説明では型クラス階層を使用しています

class              Functor f
class Functor m => Monad m

Haskellでは、現在の標準的な階層は

class                  Functor f
class Functor p     => Applicative p
class Applicative m => Monad m

すべてのモナドがファンクタであるだけでなく、すべてのアプリケーションはファンクタであり、すべてのモナドもアプリケーションです。

リストモナドを使用して、命令型の疑似コード

for a in (1, ..., 10)
   for b in (1, ..., 10)
      p <- a * b
      if even(p)
         yield p

おおまかにdo blockに変換され、

do a <- [1 .. 10]
   b <- [1 .. 10]
   let p = a * b
   guard (even p)
   return p

同等のモナド内包表記

[ p | a <- [1 .. 10], b <- [1 .. 10], let p = a * b, even p ]

そして表現

[1 .. 10] >>= (\ a ->
   [1 .. 10] >>= (\ b ->
      let p = a * b in
         guard (even p) >>       -- [ () | even p ] >>
            return p
      )
   )

表記法とモナド内包表記は、ネストされたバインド式の構文糖衣です。bind演算子は、モナディック結果のローカル名バインディングに使用されます。

let x = v in e    =   (\ x -> e)  $  v   =   v  &  (\ x -> e)
do { r <- m; c }  =   (\ r -> c) =<< m   =   m >>= (\ r -> c)

どこ

(&) :: a -> (a -> b) -> b
(&) = flip ($)

infixl 0 &

ガード機能が定義されています

guard :: Additive m => Bool -> m ()
guard True  = return ()
guard False = fail

ここで、ユニットタイプまたは「空のタプル」

data () = ()

選択失敗をサポートする追加モナドは、型クラスを使用して抽象化できます

class Monad m => Additive m where
   fail  :: m t
   (<|>) :: m t -> m t -> m t

infixl 3 <|>

instance Additive Maybe where
   fail = Nothing

   Nothing <|> m = m
   m       <|> _ = m

instance Additive [] where
   fail = []
   (<|>) = (++)

どこでモノイドfail<|>形成するforall k l m.

     k <|> fail  =  k
     fail <|> l  =  l
(k <|> l) <|> m  =  k <|> (l <|> m)

そしてfail加法モナドの吸収/消滅ゼロ要素です

_ =<< fail  =  fail

の場合

guard (even p) >> return p

even pがtrueの場合、ガードはを生成[()]し、の定義により>>、ローカル定数関数

\ _ -> return p

結果に適用され()ます。falseの場合、ガードはリストモナドfail[])を生成します。これにより、クライスリ矢印が適用される結果が得られないため、>>これpはスキップされます。

状態

悪名高いことに、モナドはステートフルな計算をエンコードするために使用されます。

状態プロセッサ機能であります

forall st t. st -> (t, st)

状態を遷移させst、結果を生成しtます。状態は st何もすることができます。何も、フラグ、カウント、配列、ハンドル、マシン、世界。

状態プロセッサのタイプは通常呼び出されます

type State st t = st -> (t, st)

状態プロセッサモナドは親切な* -> *ファンクタState stです。状態プロセッサモナドのKleisli矢印は関数です

forall st a b. a -> (State st) b

正規のHaskellでは、状態プロセッサモナドの遅延バージョンが定義されています

newtype State st t = State { stateProc :: st -> (t, st) }

instance Functor (State st) where
   map :: (a -> b) -> ((State st) a -> (State st) b)
   map f (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  (f x, s1)

instance Monad (State st) where
   return :: t -> (State st) t
   return x = State $ \ s -> (x, s)

   (=<<) :: (a -> (State st) b) -> (State st) a -> (State st) b
   f =<< (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  stateProc (f x) s1

状態プロセッサは、初期状態を提供することによって実行されます。

run :: State st t -> st -> (t, st)
run = stateProc

eval :: State st t -> st -> t
eval = fst . run

exec :: State st t -> st -> st
exec = snd . run

状態へのアクセスは、プリミティブgetputステートフルモナドを抽象化するメソッドによって提供されます。

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

class Monad m => Stateful m st m -> st where
   get :: m st
   put :: st -> m ()

m -> stモナドの状態タイプの機能依存を宣言します。すなわち、例えば、状態の種類があることを決定する一意。stmState tt

instance Stateful (State st) st where
   get :: State st st
   get = State $ \ s -> (s, s)

   put :: st -> State st ()
   put s = State $ \ _ -> ((), s)

voidC と同様に使用される単位タイプ

modify :: Stateful m st => (st -> st) -> m ()
modify f = do
   s <- get
   put (f s)

gets :: Stateful m st => (st -> t) -> m t
gets f = do
   s <- get
   return (f s)

gets レコードフィールドアクセサーでよく使用されます。

変数スレッディングに相当する状態モナド

let s0 = 34
    s1 = (+ 1) s0
    n = (* 12) s1
    s2 = (+ 7) s1
in  (show n, s2)

ここでs0 :: Int、は同様に参照的に透明ですが、無限にエレガントで実用的です

(flip run) 34
   (do
      modify (+ 1)
      n <- gets (* 12)
      modify (+ 7)
      return (show n)
   )

modify (+ 1)と同等の効果State Int ()を除いて、タイプの計算です。return ()

(flip run) 34
   (modify (+ 1) >>
      gets (* 12) >>= (\ n ->
         modify (+ 7) >>
            return (show n)
      )
   )

連想のモナドの法則は、 >>= forall m f g.

(m >>= f) >>= g  =  m >>= (\ x -> f x >>= g)

または

do {                 do {                   do {
   r1 <- do {           x <- m;                r0 <- m;
      r0 <- m;   =      do {            =      r1 <- f r0;
      f r0                 r1 <- f x;          g r1
   };                      g r1             }
   g r1                 }
}                    }

式指向プログラミング(Rustなど)の場合と同様に、ブロックの最後のステートメントはその生成量を表します。バインド演算子は、「プログラム可能なセミコロン」と呼ばれることもあります。

構造化された命令型プログラミングからの反復制御構造プリミティブがモナド的にエミュレートされます

for :: Monad m => (a -> m b) -> [a] -> m ()
for f = foldr ((>>) . f) (return ())

while :: Monad m => m Bool -> m t -> m ()
while c m = do
   b <- c
   if b then m >> while c m
        else return ()

forever :: Monad m => m t
forever m = m >> forever m

入出力

data World

I / Oワールドステートプロセッサモナドは、純粋なHaskellと現実の世界を、機能を表す、そして命令型の操作セマンティクスで調整したものです。実際の厳密な実装の類似物:

type IO t = World -> (t, World)

相互作用は不純なプリミティブによって促進されます

getChar         :: IO Char
putChar         :: Char -> IO ()
readFile        :: FilePath -> IO String
writeFile       :: FilePath -> String -> IO ()
hSetBuffering   :: Handle -> BufferMode -> IO ()
hTell           :: Handle -> IO Integer
. . .              . . .

IOプリミティブを使用するコードの不純は、型システムによって永続的にプロトコル化されます。純度がで何が起こるか、驚くばかりであるためIOにとどまり、IO

unsafePerformIO :: IO t -> t

または、少なくとも、すべきです。

Haskellプログラムの型シグニチャー

main :: IO ()
main = putStrLn "Hello, World!"

に拡大する

World -> ((), World)

世界を変える機能。

エピローグ

オブジェクトwhichesがHaskell型で、射morphismsがHaskell型間の関数であるカテゴリは、「高速で緩い」カテゴリHaskです。

ファンクタTは、カテゴリCからカテゴリへのマッピングDです。各オブジェクトのCオブジェクト内D

Tobj :  Obj(C) -> Obj(D)
   f :: *      -> *

そして、各射のためのC射でD

Tmor :  HomC(X, Y) -> HomD(Tobj(X), Tobj(Y))
 map :: (a -> b)   -> (f a -> f b)

ここでXYはのオブジェクトですCHomC(X, Y)ある準同型クラスのすべての射のX -> YではC。関手は射のアイデンティティと組成、の「構造」を維持しなければならないCで、D

                    Tmor    Tobj

      T(id)  =  id        : T(X) -> T(X)   Identity
T(f) . T(g)  =  T(f . g)  : T(X) -> T(Z)   Composition

カテゴリクライスリカテゴリC、クライスリトリプルによって指定されます

<T, eta, _*>

内部ファンクターの

T : C -> C

f)、恒等射etareturn)、および拡張演算子*=<<)。

の各クライスリ射 Hask

      f :  X -> T(Y)
      f :: a -> m b

拡張演算子による

   (_)* :  Hom(X, T(Y)) -> Hom(T(X), T(Y))
  (=<<) :: (a -> m b)   -> (m a -> m b)

HaskのKleisliカテゴリーに射が与えられている

     f* :  T(X) -> T(Y)
(f =<<) :: m a  -> m b

Kleisliカテゴリーの構成.Tは拡張の観点から与えられます

 f .T g  =  f* . g       :  X -> T(Z)
f <=< g  =  (f =<<) . g  :: a -> m c

カテゴリー公理を満たす

       eta .T g  =  g                :  Y -> T(Z)   Left identity
   return <=< g  =  g                :: b -> m c

       f .T eta  =  f                :  Z -> T(U)   Right identity
   f <=< return  =  f                :: c -> m d

  (f .T g) .T h  =  f .T (g .T h)    :  X -> T(U)   Associativity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d

等価変換を適用する

     eta .T g  =  g
     eta* . g  =  g               By definition of .T
     eta* . g  =  id . g          forall f.  id . f  =  f
         eta*  =  id              forall f g h.  f . h  =  g . h  ==>  f  =  g

(f .T g) .T h  =  f .T (g .T h)
(f* . g)* . h  =  f* . (g* . h)   By definition of .T
(f* . g)* . h  =  f* . g* . h     . is associative
    (f* . g)*  =  f* . g*         forall f g h.  f . h  =  g . h  ==>  f  =  g

拡張に関しては、標準的に与えられます

               eta*  =  id                 :  T(X) -> T(X)   Left identity
       (return =<<)  =  id                 :: m t -> m t

           f* . eta  =  f                  :  Z -> T(U)      Right identity
   (f =<<) . return  =  f                  :: c -> m d

          (f* . g)*  =  f* . g*            :  T(X) -> T(Z)   Associativity
(((f =<<) . g) =<<)  =  (f =<<) . (g =<<)  :: m a -> m c

モナドはmu、と呼ばれるプログラミングにおいて、クライスリアン拡張ではなく、自然な変換という用語で定義することもできjoinます。モナドは、内部関数のmuカテゴリのトリプルとして定義されますC

     T :  C -> C
     f :: * -> *

そして2つの自然な変化

   eta :  Id -> T
return :: t  -> f t

    mu :  T . T   -> T
  join :: f (f t) -> f t

同値を満たす

       mu . T(mu)  =  mu . mu               :  T . T . T -> T . T   Associativity
  join . map join  =  join . join           :: f (f (f t)) -> f t

      mu . T(eta)  =  mu . eta       =  id  :  T -> T               Identity
join . map return  =  join . return  =  id  :: f t -> f t

次にモナド型クラスが定義されます

class Functor m => Monad m where
   return :: t -> m t
   join   :: m (m t) -> m t

muオプションモナドの標準的な実装:

instance Monad Maybe where
   return = Just

   join (Just m) = m
   join Nothing  = Nothing

concat機能

concat :: [[a]] -> [a]
concat (x : xs) = x ++ concat xs
concat []       = []

あるjoinリストモナドのは。

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> ([a] -> [b])
   (f =<<) = concat . map f

の実装はjoin、同等性を使用して拡張形式から変換できます。

     mu  =  id*           :  T . T -> T
   join  =  (id =<<)      :: m (m t) -> m t

muから拡張形式への逆変換は、

     f*  =  mu . T(f)     :  T(X) -> T(Y)
(f =<<)  =  join . map f  :: m a -> m b

しかし、なぜそんなに抽象的な理論がプログラミングに役立つのでしょうか?

答えは簡単です。コンピュータ科学者として、私たちは抽象化大切にしています!我々はソフトウェアコンポーネントへのインタフェースを設計するとき、我々はしたいが、実装についてできるだけ明らかにすること。実装を多くの代替手段、同じ「概念」の多くの他の「インスタンス」に置き換えることができるようにしたいと考えています。多くのプログラムライブラリへの汎用インターフェイスを設計する場合、選択するインターフェイスにさまざまな実装があることがさらに重要です。私たちが非常に高く評価しているのはモナド概念の一般性です。それはカテゴリー理論が非常に抽象的であり、その概念がプログラミングに非常に役立つためです。

したがって、以下に示すモナドの一般化も、カテゴリー理論と密接な関係があることは驚くに値しません。しかし、私たちの目的は非常に実用的であることを強調します。「カテゴリ理論を実装する」ことではなく、コンビネータライブラリを構築するより一般的な方法を見つけることです。数学者が私たちのためにすでに多くの仕事をしてきたのは、単に私たちの幸運です!

矢印に一般化モナドジョン・ヒューズ


4

世界に必要なのは別のモナドブログ投稿ですが、これは野生の既存のモナドを識別するのに役立つと思います。

シェルピンスキーの三角形

上記はシェルピンスキーの三角形と呼ばれるフラクタルで、私が覚えている唯一のフラクタルです。フラクタルは、上の三角形のような自己相似構造であり、部分は全体に似ています(この場合、親の三角形の縮尺のちょうど半分)。

モナドはフラクタルです。モナディックデータ構造が与えられた場合、その値を組み合わせてデータ構造の別の値を形成できます。これがプログラミングに役立つ理由であり、多くの状況で発生する理由です。


3
「世界に必要ないもの...」という意味ですか?いいアナロジー!
groverboy、2015年

@ icc97あなたは正しい-意味は十分に明確です。意図しない皮肉、作者への謝罪。
groverboy 2017年

世界が必要としているのは、皮肉を確認する別のコメントスレッドです、注意深く読んだ場合、私は書きました、それによって明確になります。
Eugene Yokota


4

以下の「{| a |m}」がモナディックデータの一部を表すものとします。をアドバタイズするデータ型a

        (I got an a!)
          /        
    {| a |m}

関数はf、モナドを作成する方法を知っていますa

       (Hi f! What should I be?)
                      /
(You?. Oh, you'll be /
 that data there.)  /
 /                 /  (I got a b.)
|    --------------      |
|  /                     |
f a                      |
  |--later->       {| b |m}

ここでは、関数fがモナドを評価しようとしますが、非難されます。

(Hmm, how do I get that a?)
 o       (Get lost buddy.
o         Wrong type.)
o       /
f {| a |m}

Funtionはf、をa使用してを抽出する方法を見つけ>>=ます。

        (Muaahaha. How you 
         like me now!?)       
    (Better.)      \
        |     (Give me that a.)
(Fine, well ok.)    |
         \          |
   {| a |m}   >>=   f

fモナドはほとんど知りませんし、>>=共謀しています。

            (Yah got an a for me?)       
(Yeah, but hey    | 
 listen. I got    |
 something to     |
 tell you first   |
 ...)   \        /
         |      /
   {| a |m}   >>=   f

しかし、彼らは実際に何について話しますか?まあ、それはモナドに依存します。アブストラクトだけで話すことは限られた用途です。理解を具体化するためには、特定のモナドでの経験が必要です。

たとえば、データ型は多分

 data Maybe a = Nothing | Just a

次のように動作するモナドインスタンスがあります...

ここで、ケースが Just a

            (Yah what is it?)       
(... hm? Oh,      |
forget about it.  |
Hey a, yr up.)    | 
            \     |
(Evaluation  \    |
time already? \   |
Hows my hair?) |  |
      |       /   |
      |  (It's    |
      |  fine.)  /
      |   /     /    
   {| a |m}   >>=   f

しかし、 Nothing

        (Yah what is it?)       
(... There      |
is no a. )      |
  |        (No a?)
(No a.)         |
  |        (Ok, I'll deal
  |         with this.)
   \            |
    \      (Hey f, get lost.) 
     \          |   ( Where's my a? 
      \         |     I evaluate a)
       \    (Not any more  |
        \    you don't.    |
         |   We're returning
         |   Nothing.)   /
         |      |       /
         |      |      /
         |      |     /
   {| a |m}   >>=   f      (I got a b.)
                    |  (This is   \
                    |   such a     \
                    |   sham.) o o  \
                    |               o|
                    |--later-> {| b |m}

そのため、Maybeモナドでは、aアドバタイズするものが実際に含まれている場合は計算を続行できますが、含まれていない場合は計算を中止します。ただし、結果はの出力ではありませんが、モナドデータの一部ですf。このため、Maybeモナドは、失敗のコンテキストを表すために使用されます。

異なるモナドは異なる動作をします。リストは、モナディックインスタンスを持つ他のタイプのデータです。これらは次のように動作します。

(Ok, here's your a. Well, its
 a bunch of them, actually.)
  |
  |    (Thanks, no problem. Ok
  |     f, here you go, an a.)
  |       |
  |       |        (Thank's. See
  |       |         you later.)
  |  (Whoa. Hold up f,      |
  |   I got another         |
  |   a for you.)           |
  |       |      (What? No, sorry.
  |       |       Can't do it. I 
  |       |       have my hands full
  |       |       with all these "b" 
  |       |       I just made.) 
  |  (I'll hold those,      |
  |   you take this, and   /
  |   come back for more  /
  |   when you're done   / 
  |   and we'll do it   / 
  |   again.)          /
   \      |  ( Uhhh. All right.)
    \     |       /    
     \    \      /
{| a |m}   >>=  f  

この場合、関数はその入力からリストを作成する方法を知っていましたが、追加の入力と追加のリストをどうするかを知りませんでした。bind >>=f、複数の出力を組み合わせることで役立ちました。この例を含めて、>>=は抽出を担当しているがa、の最終的なバインド出力にもアクセスできることを示しfます。実際、a最終的な出力に同じタイプのコンテキストがあることがわかっている場合を除いて、何も抽出されません。

異なるコンテキストを表すために使用される他のモナドがあります。さらにいくつかの特徴を以下に示します。IOモナドは実際にはありませんaが、それは男を知っているし、それを取得しますa、あなたのために。State stモナドはの秘密の隠し場所があるst、それはに渡すことfにもかかわらず、テーブルの下fだけを求めに来たのaReader rモナドはに似ているState st、それだけでできますが、f見てr

これらすべての要点は、それ自体がモナドであると宣言されているすべてのタイプのデータが、モナドからの値の抽出に関する何らかのコンテキストを宣言しているということです。このすべてから大きな利益は?まあ、なんらかのコンテキストで計算を行うのは簡単です。ただし、複数のコンテキストが含まれる計算をつなぎ合わせると、面倒になる可能性があります。モナド操作は、プログラマーが必要としないように、コンテキストの相互作用を解決します。

を使用すると、>>=から自律性の一部を取り除くことにより、混乱を緩和できますf。つまりNothing、たとえば上記の場合、の場合はf何をすべきかを決定することができなくなりNothingます。でエンコードされてい>>=ます。これはトレードオフです。以下のためにそれが必要だった場合fの場合には何をすべきかを決定するためにNothing、その後fから機能していなければならないMaybe aMaybe b。この場合、Maybeモナドであることは無関係です。

ただし、データ型がそのコンストラクター(IOを確認)をエクスポートしない場合があることに注意してください。アドバタイズされた値を操作する場合は、モナディックインターフェイスを使用するしかありません。


3

モナドは、状態が変化するオブジェクトをカプセル化するために使用されるものです。他の方法では変更可能な状態を許可しない言語(Haskellなど)で最も頻繁に発生します。

例として、ファイルI / Oがあります。

ファイルI / Oにモナドを使用して、状態の変化の性質を、モナドを使用したコードだけに分離できます。モナド内のコードはモナド外の世界の変化する状態を効果的に無視することができます-これはあなたのプログラムの全体的な効果について推論することをずっと簡単にします。


3
私が理解しているように、モナドはそれ以上のものです。可変状態を「純粋な」関数型言語でカプセル化することは、モナドの1つのアプリケーションにすぎません。
thSoftは
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.