分かりやすい英語のモナド?(FPのバックグラウンドがないOOPプログラマー向け)


743

OOPプログラマーが(関数型プログラミングのバックグラウンドなしで)理解できるという点で、モナドとは何ですか?

それはどのような問題を解決し、それが使用される最も一般的な場所は何ですか?

編集:

私が探していた理解の種類を明確にするために、モナドを持つFPアプリケーションをOOPアプリケーションに変換するとします。モナドの責任をOOPアプリに移植するにはどうしますか?


10
このブログ投稿は非常に優れています:blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html
Pascal Cuoq 2010


10
@Pavel:エリックから下で得た答えは、(FPの背景ではなく)OOの背景を持つ人々に推奨される他のQの答えよりもはるかに優れています。
ドナルフェロー

5
@Donal:これだまされている場合(私はそれについて意見はありません)、良い答えをオリジナルに追加する必要があります。つまり、適切な回答がクローズを重複として排除することはありません。それが十分に近い複製である場合、これはマージとしてモデレーターによって達成できます。
dmckee ---元モデレーターの子猫2010

回答:


732

更新:この質問は、Monadsで読むことができる非常に長いブログシリーズの主題でした。すばらしい質問をありがとう!

OOPプログラマーが(関数型プログラミングのバックグラウンドなしで)理解できるという点で、モナドとは何ですか?

モナドはあるタイプの「増幅器」特定の規則に従うと、提供される特定の動作を有します

まず、「タイプのアンプ」とは何ですか?つまり、型を取得してそれをより特殊な型に変換できるシステムを意味します。たとえば、C#ではを検討してくださいNullable<T>。これはタイプのアンプです。たとえばint、型を取得して、その型に新しい機能を追加できます。つまり、以前はできなかったときにnullにできるようになりました。

2番目の例として、を検討してくださいIEnumerable<T>。タイプのアンプです。たとえば、型を取得して、stringその型に新しい機能を追加できます。つまり、任意の数の単一の文字列から文字列のシーケンスを作成できるようになります。

「特定のルール」とは何ですか?簡単に言うと、基礎となる型の関数が、関数型合成の通常の規則に従うように、増幅型で機能する賢明な方法があるということです。たとえば、整数に対する関数がある場合、

int M(int x) { return x + N(x * 2); }

次に、対応する関数on Nullable<int>は、そこで行われたすべての演算子と呼び出しを、以前と同じように連動させます。

(これは信じられないほどあいまいで不正確です。機能構成の知識について何も想定していない説明を求めました。)

「操作」とは何ですか?

  1. プレーン型から値を取り、同等のモナド値を作成する「ユニット」操作(混乱して「リターン」操作と呼ばれることもあります)があります。これは、本質的に、非増幅型の値を取り、それを増幅型の値に変換する方法を提供します。オブジェクト指向言語のコンストラクターとして実装できます。

  2. モナドの値と値を変換できる関数を受け取り、新しいモナドの値を返す「バインド」演算があります。バインドは、モナドのセマンティクスを定義する主要な操作です。これにより、前述の関数構成の規則に従って、非増幅型の演算を増幅型の演算に変換できます。

  3. 多くの場合、非増幅型を増幅型から戻す方法があります。厳密に言えば、この操作はモナドを持つ必要はありません。(ただしコマンドを使用する場合は必要です。この記事ではこれについては考慮しません。)

もう一度、Nullable<T>例を見てみましょう。コンストラクタでをにint変換できますNullable<int>。C#コンパイラーは、null許容の「リフティング」のほとんどを処理しますが、そうでない場合、リフティング変換は簡単です。

int M(int x) { whatever }

に変換されます

Nullable<int> M(Nullable<int> x) 
{ 
    if (x == null) 
        return null; 
    else 
        return new Nullable<int>(whatever);
}

そして、プロパティにNullable<int>戻ることintは行われValueます。

重要なのは関数変換です。null許容操作の実際のセマンティクス(null伝播の操作が伝播することnull)が変換でどのようにキャプチャされるかに注意してください。これを一般化できます。

元の関数のように、関数intto があるとします。これは、null許容コンストラクターを介して結果を実行するだけなので、を受け取ってを返す関数に簡単に作成できます。次の高次メソッドがあるとします。intMintNullable<int>

static Nullable<T> Bind<T>(Nullable<T> amplified, Func<T, Nullable<T>> func)
{
    if (amplified == null) 
        return null;
    else
        return func(amplified.Value);
}

あなたはそれで何ができるか見てください?を受け取ってintを返すメソッドint、またはを受け取ってintを返すメソッドにNullable<int>は、null許容のセマンティクスを適用できるようになりました

さらに:2つの方法があるとします

Nullable<int> X(int q) { ... }
Nullable<int> Y(int r) { ... }

そしてあなたはそれらを作成したい:

Nullable<int> Z(int s) { return X(Y(s)); }

つまりZXおよびの構成ですY。しかし、をX受け取りint、をY返すため、これを行うことはできませんNullable<int>。しかし、「バインド」操作があるので、これを機能させることができます。

Nullable<int> Z(int s) { return Bind(Y(s), X); }

モナドのバインド操作は、増幅された型の関数の構成を機能させるものです。上記で私が手を振った「ルール」は、モナドが通常の関数構成のルールを保持するということです。恒等関数で構成すると、元の関数になり、その構成は連想になります。

C#では、「Bind」は「SelectMany」と呼ばれます。それがシーケンスモナドでどのように機能するか見てみましょう。値をシーケンスに変換することと、シーケンスに対する操作をバインドすることの2つが必要です。おまけとして、「シーケンスを値に戻す」こともできます。これらの操作は次のとおりです。

static IEnumerable<T> MakeSequence<T>(T item)
{
    yield return item;
}
// Extract a value
static T First<T>(IEnumerable<T> sequence)
{
    // let's just take the first one
    foreach(T item in sequence) return item; 
    throw new Exception("No first item");
}
// "Bind" is called "SelectMany"
static IEnumerable<T> SelectMany<T>(IEnumerable<T> seq, Func<T, IEnumerable<T>> func)
{
    foreach(T item in seq)
        foreach(T result in func(item))
            yield return result;            
}

nullableモナドルールは、「nullableを生成する2つの関数を組み合わせて、内側の関数がnullになるかどうかを確認することです。もしそうであれば、nullを生成し、そうでない場合は、外側の関数を結果で呼び出します」。これがnullableの望ましいセマンティクスです。

シーケンスモナドルールは、「シーケンスを生成する2つの関数を組み合わせ、外部関数を内部関数によって生成されたすべての要素に適用し、結果のシーケンスをすべて連結すること」です。モナドの基本的なセマンティクスは、Bind/ SelectManyメソッドで取得されます。これは、モナドが実際に何を意味するかを伝えるメソッドです。

私たちはもっとうまくやることができます。intのシーケンスと、intを取り、文字列のシーケンスを生成するメソッドがあるとします。バインディング操作を一般化して、一方の入力がもう一方の出力と一致する限り、さまざまな増幅された型を受け取って返す関数の構成を許可できます。

static IEnumerable<U> SelectMany<T,U>(IEnumerable<T> seq, Func<T, IEnumerable<U>> func)
{
    foreach(T item in seq)
        foreach(U result in func(item))
            yield return result;            
}

これで、「この一連の個々の整数を整数のシーケンスに増幅します。この特定の整数を一連の文字列に変換し、一連の文字列に増幅します。次に、両方の演算を組み合わせます。この一連の整数を次の連結に増幅します。文字列のすべてのシーケンス。」モナドを使用すると、増幅を構成できます。

それはどのような問題を解決し、それが使用される最も一般的な場所は何ですか?

これは、「シングルトンパターンでどのような問題が解決されるのか」と尋ねるようなものですが、試してみます。

モナドは通常、次のような問題を解決するために使用されます。

  • このタイプの新しい機能を作成し、このタイプの古い機能を組み合わせて新しい機能を使用する必要があります。
  • タイプに対する一連の操作をキャプチャし、それらの操作を構成可能なオブジェクトとして表現し、適切な一連の操作が表現されるまで次第に大きくなる構成を構築し、それから結果を取得する必要があります
  • 副作用を嫌う言語で副作用のある操作をきれいに表現する必要がある

C#はその設計でモナドを使用します。すでに述べたように、ヌル可能パターンは「たぶんモナド」に非常に似ています。LINQは完全にモナドで構築されています。SelectManyこの方法は、オペレーションの構図の意味の作業を行うものです。(Erik Meijerは、すべてのLINQ関数が実際にによって実装される可能性があることを指摘するのが好きSelectManyです。それ以外はすべて便利です。)

私が探していた理解の種類を明確にするために、モナドを持つFPアプリケーションをOOPアプリケーションに変換するとします。モナドの責任をOOPアプリに移植するにはどうしますか?

ほとんどのOOP言語には、モナドパターン自体を直接表現するための十分な型システムがありません。ジェネリック型よりも高い型である型をサポートする型システムが必要です。だから私はそれをしようとはしません。むしろ、各モナドを表すジェネリック型を実装し、必要な3つの操作を表すメソッドを実装します。値を増幅値に変換する(たぶん)増幅値を値に変換する、および非増幅値の関数をに変換する増幅された値の関数。

開始するには、C#でLINQを実装する方法が適切です。SelectManyメソッドを研究します。これは、C#でシーケンスモナドがどのように機能するかを理解するための鍵です。これは非常に単純な方法ですが、非常に強力です!


推奨、さらに読む:

  1. C#でのモナドのより詳細で理論的に正しい説明については、このトピックに関する私の(Eric Lippertの)同僚のWes Dyerの記事を強くお勧めします。この記事は、モナドが私にようやく「クリック」したときに私にモナドを説明したものです。
  2. なぜモナドが必要なのかを示す良い例です(例ではHaskellを使用しています)
  3. 前の記事をJavaScriptに「翻訳」したようなものです。


17
これは素晴らしい答えですが、私の頭はアスプロディになった。私は今週末にフォローアップしてそれをじっと見つめ、物事が落ち着いて私の頭の中で理にかなっていないかどうかあなたに質問します。
ポールネイサン

5
いつものようにエリック。より理論的な(しかし非常に興味深い)ディスカッションのために、MinLINQに関するBart De Smetのブログ投稿が、いくつかの関数型プログラミング構造をC#にも関連付けるのに役立つことがわかりました。community.bartdesmet.net/blogs/bart/archive/2010/01/01/...
ロンWarholic

41
タイプを増幅するのではなく、タイプを増強すると言う方が私には理にかなっています。
Gabe

61
@slomojo:それを私が書いて書いたつもりだったものに戻しました。あなたとGabeがあなた自身の答えを書きたいなら、あなたはすぐに行きます。
Eric Lippert、

24
@Eric、もちろんあなた次第ですが、Amplifierは既存のプロパティがブーストされることを意味し、誤解を招きます。
ocodo '19年

341

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

  1. 関数だけでプログラミングしたい。(結局のところ「関数型プログラミング」-FP)。
  2. 次に、最初の大きな問題があります。これはプログラムです:

    f(x) = 2 * x

    g(x,y) = x / y

    最初に何を実行するかをどのように言うことができ ますか?関数だけを使用して、順序付けられた関数のシーケンス(つまり、プログラム)をどのように形成できますか?

    解決策:関数の作成。最初に必要な場合gf、次のように書いてくださいf(g(x,y))。OK、でも...

  3. その他の問題:一部の関数は失敗する可能性があります(つまりg(2,0)、0で除算)。FPには「例外」はありません。どうすれば解決できますか?

    解決策:関数が2つの種類のものを返すことを許可しましょう:g : Real,Real -> Real(2つの実数から1つの実数への関数)を持つ代わりに、(g : Real,Real -> Real | Nothing2つの実数から(実数または何もない)への関数)を許可しましょう。

  4. しかし、関数は(より簡単に)1つのものだけを返す必要があります。

    解決策:返される新しいタイプのデータを作成してみましょう。「ボクシングタイプ」は、実在するものか、まったく存在しないものを囲みます。したがって、私たちはを持つことができますg : Real,Real -> Maybe Real。OK、でも...

  5. 今はどうなりf(g(x,y))ますか?fを使用する準備ができていませんMaybe Real。そして、私たちはと接続することができ、すべての機能を変更したくないg消費するようにMaybe Real

    解決策:関数を「接続」/「作成」/「リンク」する特別な関数を用意しましょう。このようにして、背後で1つの関数の出力を調整して、次の関数にフィードできます。

    私たちの場合:(に g >>= f接続/構成gするf)。の出力>>=を取得しg、それを検査したい場合は、Nothing単に呼び出しfて返さない場合に限りますNothing。または逆に、箱に入っているものを取り出し、それと一緒にRealfをやります。(このアルゴリズムは>>=Maybe型のの実装にすぎません)。

  6. 同じパターンを使用して解決できる他の多くの問題が発生します。1.「ボックス」を使用してさまざまな意味/値をコード化/保存し、gそれらの「ボックス化された値」を返すような関数を使用します。2. の出力をの入力にg >>= f接続するのに役立つコンポーザー/リンカーがあるため、まったく変更する必要はありません。gff

  7. この手法を使用して解決できる注目すべき問題は次のとおりです。

    • 関数のシーケンス内のすべての関数(「プログラム」)が共有できるグローバルな状態:ソリューションStateMonad

    • 「純粋でない関数」:同じ入力に対して異なる出力を生成する関数は好きではありません。したがって、これらの関数にマークを付けて、タグ付き/ボックス化された値を返すようにします:モナド。IO

トータルハピネス!!!!


2
@DmitriZaitsev例外は、私が知る限り、「純粋でないコード」(IOモナド)でのみ発生します。
cibercitizen1

3
@DmitriZaitsev Nothingの役割は、他のタイプ(期待されるRealとは異なる)によって果たすことができます。それはポイントではありません。この例では、問題は、前のものが予期しない値の型を次のものに返す可能性がある場合に、後者をチェーンせずに関数をチェーンに適応させる方法です(入力としてRealのみを受け入れます)。
cibercitizen1

3
-もう一つの混乱のポイントは、単語があなたの答えに二回しか表示され、唯一の他の用語と組み合わせて「モナド」ということであるStateIO特定されたそれらのどれも同様に「モナド」の正確な意味で、
ドミトリザイツェフ

31
OOPのバックグラウンドを持つ人としての私にとって、この答えはモナドを持っている背後にある動機と、モナドが実際に何であるかをよく説明していました(はるかに受け入れられた答えです)。それで、私はそれがとても役に立ちます。@ cibercitizen1と+1に感謝します
無断で

3
約1年間、関数型プログラミングについて何度も読んでいます。この答え、特に最初の2つのポイントにより、命令型プログラミングが実際に何を意味するのか、そして関数型プログラミングがなぜ異なるのかを理解することができました。ありがとうございました!
jrahhali

82

モナドに最も近いオブジェクト指向のアナロジーは「コマンドパターン」です。

コマンドパターンでは、通常のステートメントまたは式をコマンドオブジェクトでラップします。コマンドオブジェクトは、ラップされたステートメントを実行する実行メソッドを公開します。したがって、ステートメントは、自由に渡して実行できるファーストクラスオブジェクトに変換されます。コマンドを構成して、コマンドオブジェクトをチェーンおよびネストしてプログラムオブジェクトを作成できます。

コマンドは、個別のオブジェクトであるinvokerによって実行されます。(一連の通常のステートメントを実行するだけでなく)コマンドパターンを使用する利点は、呼び出し元ごとにコマンドの実行方法に異なるロジックを適用できることです。

コマンドパターンを使用して、ホスト言語でサポートされていない言語機能を追加(または削除)できます。たとえば、例外のない架空のオブジェクト指向言語では、「try」メソッドと「throw」メソッドをコマンドに公開することで、例外セマンティクスを追加できます。コマンド呼び出しがスローする場合、呼び出し元は最後の「試行」呼び出しまでコマンドのリスト(またはツリー)を逆戻りします。逆に、個々のコマンドによってスローされたすべての例外をキャッチし、それらをエラーコードに変換して次のコマンドに渡すことにより、言語から例外セマンティックを削除できます(例外が悪いと思われる場合)。

トランザクション、非決定的実行、または継続などのさらに豪華な実行セマンティクスは、ネイティブではサポートされていない言語でこのように実装できます。考えてみればかなりパワフルなパターンです。

現在、実際には、コマンドパターンはこのような一般的な言語機能として使用されていません。各ステートメントを個別のクラスに変換するオーバーヘッドは、耐えがたい量の定型コードにつながります。しかし、原則として、モナドがfpで解決するために使用されるのと同じ問題を解決するために使用できます。


15
これは、関数型プログラミングの概念に依存せず、実際のOOP用語で表現したモナドの最初の説明だと思います。本当に良い答えです。
デビッドK.ヘス

これは、モナドが実際にFP / Haskellにあるものに非常に近い2です。ただし、コマンドオブジェクト自体は、それらが属する「呼び出しロジック」を「認識」しています(また、互換性のあるものだけをチェーンできます)。呼び出し側は最初の値を提供するだけです。「非確定的実行ロジック」によって「印刷」コマンドを実行できるようではありません。いいえ、「I / Oロジック」(つまり、IOモナド)でなければなりません。しかし、それ以外は非常に近いです。モナドは単なるプログラム(コードステートメントで構築され、後で実行される)であるとも言えます。当初、「バインド」は「プログラム可能なセミコロン」と呼ばれていました。
ネスは

1
@ DavidK.Hess私は確かに、FPを使用して基本的なFPの概念を説明する回答、特にScalaのようなFP言語を使用する回答に信じられないほど懐疑的です。よくやった、JacquesB!
モニカを

62

OOPプログラマーが(関数型プログラミングのバックグラウンドなしで)理解できるという点で、モナドとは何ですか?

それはどのような問題を解決し、それが最もよく使用される場所は何ですか?それが最もよく使用される場所ですか?

オブジェクト指向プログラミングの観点から、モナドは、2つのメソッドを持つタイプによってパラメーター化されたインターフェース(または、より可能性の高いミックスイン)でreturnあり、以下bindを記述します。

  • 値を注入して、その注入された値型のモナド値を取得する方法。
  • モナディック値に対して、モナディックでない値からモナディック値を作成する関数の使用方法。

それが解決する問題は、どのインターフェイスからも予想されるのと同じタイプの問題です。つまり、「私はさまざまなことをするさまざまなクラスがたくさんありますが、根本的な類似性を持つ方法でそれらのさまざまなことをしているようです。方法クラス自体が実際には「オブジェクト」クラス自体よりも近いもののサブタイプでなくても、それらの類似性を説明できますか?」

より具体的には、Monad「インターフェース」は、それ自体が型をとる型と似ている、IEnumeratorまたはIIteratorそれをとるという点で似ています。Monadただし、メインの「ポイント」は、メインのクラスの情報構造を維持または強化しながら、内部タイプに基づいて操作を新しい「内部タイプ」を持つポイントに接続できることです。


1
returnモナドのインスタンスを引数として取りませんので、実際にはモナドのメソッドではありません。(つまり、これ/自分はありません)
ローレンスゴンサルベス

@LaurenceGonsalves:私は現在、学士論文のためにこれを調べているので、C#/ Javaのインターフェースに静的メソッドが欠けていることが主な制限事項だと思います。タイプクラスに基づく代わりに、少なくとも静的にバインドされて、モナドストーリー全体を実装する方向に遠くまで行くことができます。興味深いことに、これはより高い種類の型がない場合でも機能します。
セバスチャングラフ

42

あなたは最近のプレゼンテーション「Monadologie-型不安に関する専門家の助け」をChristopher League(2010年7月12日)で発表しました。これは継続とモナドのトピックについて非常に興味深いものです。
この(スライドシェア)プレゼンテーションに関連するビデオは、実際にはvimeoで入手できます
モナドのパートは、この1時間のビデオで約37分で始まり、58枚のスライドのプレゼンテーションのスライド42から始まります。

「関数型プログラミングの主要な設計パターン」として提示されていますが、例で使用されている言語はOOPであり関数型でもあるScalaです。
あなたはブログ記事「でScalaでモナドで続きを読むことができます- Scalaでは抽象計算する別の方法モナドから、」Debasishゴーシュ(2008年3月27日)。

コンストラクタ Mは、次の演算をサポートする場合、モナドです。

# the return function
def unit[A] (x: A): M[A]

# called "bind" in Haskell 
def flatMap[A,B] (m: M[A]) (f: A => M[B]): M[B]

# Other two can be written in term of the first two:

def map[A,B] (m: M[A]) (f: A => B): M[B] =
  flatMap(m){ x => unit(f(x)) }

def andThen[A,B] (ma: M[A]) (mb: M[B]): M[B] =
  flatMap(ma){ x => mb }

したがって、たとえば(Scalaの場合):

  • Option モナドです
    def unit [A](x:A):Option [A] = Some(x)

    def flatMap [A​​、B](m:Option [A])(f:A => Option [B]):Option [B] =
      mマッチ{
       ケースなし=>なし
       ケースSome(x)=> f(x)
      }
  • List モナドです
    def unit [A](x:A):List [A] = List(x)

    def flatMap [A​​、B](m:List [A])(f:A => List [B]):List [B] =
      mマッチ{
        ケースNil => Nil
        ケースx :: xs => f(x)::: flatMap(xs)(f)
      }

Monadの構造を利用するために構築された便利な構文のため、MonadはScalaで大きな問題です。

forScalaでの理解

for {
  i <- 1 to 4
  j <- 1 to i
  k <- 1 to j
} yield i*j*k

コンパイラによって次のように変換されます。

(1 to 4).flatMap { i =>
  (1 to i).flatMap { j =>
    (1 to j).map { k =>
      i*j*k }}}

重要な抽象化はでありflatMap、これはチェーンを通じて計算をバインドします。
を呼び出すたびにflatMap、同じデータ構造タイプ(値は異なります)が返されます。これは、チェーン内の次のコマンドへの入力として機能します。

上記のスニペットでは、flatMapは入力としてクロージャ(SomeType) => List[AnotherType]を取り、を返しますList[AnotherType]。注意すべき重要な点は、すべてのflatMapが同じクロージャタイプを入力として受け取り、同じタイプを出力として返すことです。

これが計算スレッドを「バインド」するものです。for内包表記のシーケンスのすべての項目は、この同じ型の制約を守る必要があります。


次のように2つの操作(失敗する可能性があります)を実行し、結果を3番目に渡す場合:

lookupVenue: String => Option[Venue]
getLoggedInUser: SessionID => Option[User]
reserveTable: (Venue, User) => Option[ConfNo]

しかし、Monadを利用しないと、次のような複雑なOOPコードが得られます。

val user = getLoggedInUser(session)
val confirm =
  if(!user.isDefined) None
  else lookupVenue(name) match {
    case None => None
    case Some(venue) =>
      val confno = reserveTable(venue, user.get)
      if(confno.isDefined)
        mailTo(confno.get, user.get)
      confno
  }

一方、モナドでは、すべての操作が機能するように実際の型(VenueUser)を操作し、オプションの検証を非表示にしておくことができます。これは、すべてfor構文のフラットマップが原因です。

val confirm = for {
  venue <- lookupVenue(name)
  user <- getLoggedInUser(session)
  confno <- reserveTable(venue, user)
} yield {
  mailTo(confno, user)
  confno
}

収量部分は、3つの関数すべてにがある場合にのみ実行されますSome[X]。いずれもNone直接に返されconfirmます。


そう:

モナドを使用すると、関数型プログラミング内で順序付けされた計算が可能になります。これにより、DSLのように、構造化された形式でアクションのシーケンスをモデル化できます。

そして、最大の力は、さまざまな目的に役立つモナドを、アプリケーション内の拡張可能な抽象化に構成する機能にあります。

モナドによるこのシーケンスとアクションのスレッド化は、クロージャの魔法を通して変換を行う言語コンパイラによって行われます。


ちなみに、モナドはFPで使用される計算のモデルだけではありません。

カテゴリ理論は、多くの計算モデルを提案しています。その中で

  • 計算の矢印モデル
  • 計算のモナドモデル
  • 計算の適用モデル

2
この説明が大好きです!あなたが与えた例は、コンセプトを美しく示しており、また、SelectMany()がモナドであることについて、EricのティーザーからIMHOに欠けていたものを追加しています。このためのTHX!
オーブン

1
私見これは最もエレガントな答えです
ポリメラーゼ2017

そして何よりもまず、ファンクター。
ネス

34

速い読者を尊重するために、私は最初に正確な定義から始め、すばやく「平易な英語」の説明を続け、次に例に移ります。

これは、簡潔に正確な定義を少し言い換えたものです。

モナド(コンピュータサイエンスのは)ことを正式マップです。

  • X特定のプログラミング言語のすべてのタイプを新しいタイプT(X)(「T値のある計算のタイプ」と呼ばれるX)に送信します。

  • フォームf:X->T(Y)と関数の2つの関数を作成するためのルールを備えてい g:Y->T(Z)ますg∘f:X->T(Z)

  • 明白な意味で連想的であり、と呼ばれる与えられた単位関数に関して統一的である方法でpure_X:X->T(X)、単純にその値を返す純粋な計算に値を取ると考えられます。

つまり、簡単に言うと、モナドは、任意の型Xから別の型T(X)に渡すルールf:X->T(Y)g:Y->T(Z)h:X->T(Z)であり、2つの関数(作成したいができない)新しい関数に渡すルールです。しかし、これ厳密な数学的意味での構成はありません。基本的に、関数の構成を「曲げる」か、関数の構成方法を再定義します。

さらに、「明白な」数理公理を満たすためには、モナドの構成規則が必要です。

  • 連想:作曲fg、その後では、h(外部からの)構成と同じでなければならないghし、次いでf(内側から)。
  • 単一性:どちらかの側で恒等関数を使用するfと、結果が得られます。f

繰り返しになりますが、簡単な言葉で言えば、関数の構成を好きなように再定義することはできません。

  • 最初に、たとえばf(g(h(k(x)))、で関数のペアを構成する順序を指定することを気にしないように、いくつかの関数を続けて構成できるようにするための結合性が必要です。モナドルールは、その公理なしで関数のペアを作成する方法を規定するだけなので、どのペアが最初に作成されるかなどを知る必要があります。(こと可換性のプロパティとは異なることに注意してくださいfで構成はg同じであったgで構成f必要とされません)。
  • そして2番目に、単一性が必要です。これは、アイデンティティが期待どおりに簡単に構成されているということです。したがって、これらのIDを抽出できるときはいつでも、関数を安全にリファクタリングできます。

つまり、簡単に言えば、モナドは、2つの公理(結合性と単位性)を満足する型拡張と合成関数のルールです。

実際には、関数を作成する言語、コンパイラ、またはフレームワークによってモナドを実装する必要があります。したがって、関数の実行がどのように実装されているかを心配するのではなく、関数のロジックの記述に集中できます。

一言で言えば、それは本質的にそれです。


プロの数学者なので、私は呼び出さないようにすることを好むhの「組成物」fg。数学的にはそうではないからです。それを「構成」と呼ぶことhは、それが本当の数学的構成であると誤って推定しますが、そうではありません。fおよびによって一意に決定されることはありませんg。代わりに、それは私たちのモナドの新しい「作曲のルール」の結果です。後者が存在していても、実際の数学的な構成とはまったく異なる場合があります。


乾きを少なくするために、小さなセクションで注釈を付ける例でそれを説明してみましょう。そのため、要点までスキップできます。

モナドの例としてスローされる例外

次の2つの関数を作成するとします。

f: x -> 1 / x
g: y -> 2 * y

しかしf(0)、定義されていないため、例外eがスローされます。では、どのように組成値を定義できますg(f(0))か?もちろん、例外を再びスローします!多分同じeです。多分新しい更新された例外e1

ここで正確に何が起こりますか?まず、新しい例外値(異なるまたは同じ)が必要です。あなたはそれらを呼び出すことができますnothingnullまたは何が、本質は同じまま-彼らはそれはすべきではない例えば、新しい値である必要がありnumber、ここで私たちの例では。特定の言語でどのように実装できるnullかとの混乱を避けるために、それらを呼び出さないようにしていnullます。同様に私は避けることを好むnothingことが多い関連付けられているためnull、原理的には、何をしている、null行う必要があります、しかし、その原理は、多くの場合、どのような実際的な理由のために曲げられます。

正確に例外とは何ですか?

これは経験豊富なプログラマにとってはささいなことですが、混乱のワームを消滅させるために少しだけ語りたいと思います。

例外は、無効な実行結果が発生した方法に関する情報をカプセル化するオブジェクトです。

これは、詳細を破棄して単一のグローバル値(NaNまたはnull)を返すことから、長いログリストまたは正確に何が起こったかを生成してデータベースに送信し、分散データストレージレイヤー全体に複製することまで、さまざまです;)

これら2つの極端な例外の例の重要な違いは、最初のケースでは副作用ないことです。第二にあります。これは(千ドル)の質問につながります。

純粋な関数で例外は許可されますか?

より短い回答:はい、ただし副作用が発生しない場合のみ。

より長い答え。純粋であるためには、関数の出力はその入力によって一意に決定される必要があります。そこで、例外と呼ぶ新しい抽象値にf送信0することで、関数を修正しますee入力によって一意に決定されない外部情報が値に含まれていないことを確認しますx。だからここに副作用のない例外の例があります:

e = {
  type: error, 
  message: 'I got error trying to divide 1 by 0'
}

そして、これは副作用のあるものです:

e = {
  type: error, 
  message: 'Our committee to decide what is 1/0 is currently away'
}

実際には、そのメッセージが将来変更される可能性がある場合にのみ副作用があります。ただし、変更されないことが保証されている場合、その値は一意に予測可能になるため、副作用はありません。

それをさらに愚かにするために。42これまでに戻る関数は明らかに純粋です。しかし、狂った誰かが42その値を変更する可能性がある変数を作成することを決定した場合、まったく同じ関数が新しい条件下で純粋でなくなる。

簡潔にするために、オブジェクトリテラル表記を使用して、本質を説明していることに注意してください。残念ながら、物事には、JavaScriptなどの言語で、めちゃくちゃされているerrorような実際の型に対し、我々は関数合成に関して、ここで望むように振る舞うタイプではありませんnullか、NaN人工的ないくつか通過し、常に直感的ではない、むしろ、このように動作しませんが型変換。

タイプ拡張

例外内でメッセージを変化させたいのでE、例外オブジェクト全体に新しいタイプを実際に宣言しています。それがmaybe number、タイプnumberまたは新しい例外タイプのいずれかになる、紛らわしい名前とは別に、Eので、それは本当にの組合number | EですnumberE。特に、それはどのように構築したいかに依存しますがE、これは名前に示唆も反映もされていませんmaybe number

機能構成とは?

これは、関数f: X -> Yを使用してg: Y -> Z、それらの合成を関数h: X -> Zを満たす関数として構築する数学演算 h(x) = g(f(x))です。この定義に関する問題は、結果がf(x)がの引数として許可されていないしますg

数学では、これらの関数は追加の作業なしでは構成できません。私たちの上の例のために厳密に数学的なソリューションfg削除することです0の定義のセットからf。その新しい定義のセット(の新しいより制限的なタイプx)により、f構成可能になりgます。

ただし、プログラミングの定義セットを制限することは、プログラミングではあまり実用的ではありません。 fような。代わりに、例外を使用できます。

それとも別のアプローチとして、人工の値が同じように作成されNaNundefinednullInfinityあなたが評価するのでなど1/0Infinityして1/-0まで-Infinity。そして、例外をスローする代わりに、新しい値を強制的に式に戻します。予測可能な結果が得られるかどうかは次のとおりです。

1/0                // => Infinity
parseInt(Infinity) // => NaN
NaN < 0            // => false
false + 1          // => 1

そして、私たちは次に進む準備ができている通常の数に戻っています;)

JavaScriptを使用すると、上記の例のようにエラーをスローすることなく、任意のコストで数式を実行し続けることができます。つまり、関数を作成することもできます。これはまさにモナドが何であるかです-これは、この回答の冒頭で定義された公理を満たす関数を構成する規則です。

しかし、モナドである数値エラーを処理するためのJavaScriptの実装から生じる関数を構成するルールは何ですか?

この質問に答えるには、公理をチェックするだけです(ここでは、質問の一部ではなく、演習として残します)。

例外のスローを使用してモナドを構築できますか?

実際、より有用なモナドは代わりにif fがsome xに対して例外をスローするように規定するルールであり、そのため、anyを使用してその構成を行いますg。さらに、例外をEグローバルに一意にして、考えられる値を1つだけにします(カテゴリ理論の最終オブジェクト)。これで2つの公理は即座にチェック可能になり、非常に便利なモナドが得られます。そしてその結果は、多分モナドとして知られているものです。


3
良い貢献。+1ただし、「ほとんどの説明が長すぎる...」を削除して、自分の説明が最も長くなるようにすることもできます。他の人は、必要に応じて「わかりやすい英語」であるかどうかを判断します。「わかりやすい英語==簡単な言葉で、簡単な方法で」という質問です。
cibercitizen1 2016年

@ cibercitizen1ありがとう!あなたが例を数えなければ、それは実際には短いです。重要な点は、定義を理解するために例を読む必要がないことです。残念ながら、多くの説明では最初例を読むことを余儀なくされていますが、これは多くの場合不要ですが、もちろんライターにとって余分な作業が必要になる場合があります。特定の例への依存度が高すぎると、重要でない詳細により画像が不明瞭になり、把握が難しくなる危険があります。そうは言っても、あなたには有効なポイントがあります。更新をご覧ください。
Dmitri Zaitsev

2
長すぎて混乱
見られたるガン

1
@seenimurugan改善提案は大歓迎です;)
Dmitri Zaitsev

26

モナドは値をカプセル化するデータ型であり、基本的に2つの操作を適用できます。

  • return x カプセル化するモナド型の値を作成します x
  • m >>= f(「バインド演算子」と読みます)fモナドの値に関数を適用しますm

それがモナドです。さらにいくつかの技術がありますが基本的にこれらの2つの演算はモナドを定義します。本当の質問は、「モナド何をするのか?」であり、それはモナドに依存します。リストはモナドであり、たぶんモナドであり、IO操作はモナドです。私たちはそれらのものがモナドであると言うとき、それが意味するすべては、彼らがのモナドのインタフェースを持っていることであるreturn>>=


「モナドが何をするか、そしてそれはモナドに依存します」:より正確には、それはbind各モナド型に対して定義されなければならない関数に依存しますね?バインドの構成が1つしかなく、バインド関数の定義が1つだけではなく、モナディックタイプごとに1つあるため、バインドと構成を混同しないようにしてください。
Hibou57 2017年

14

ウィキペディアから:

関数型プログラミングでは、モナドは、(ドメインモデルのデータの代わりに)計算を表すために使用される一種の抽象的なデータ型です。モナドを使用すると、プログラマーはアクションをチェーンしてパイプラインを構築できます。パイプラインでは、各アクションがモナドによって提供される追加の処理ルールで装飾されます。関数型で記述されたプログラムは、モナドを使用して、シーケンス演算を含む手続きを構築できます1 [2]たり、任意の制御フロー(同時実行、継続、例外の処理など)を定義したりできます。

正式には、モナドは2つの操作(バインドとリターン)と型コンストラクタMを定義することによって構築されます。型コンストラクタMは、モナド関数(モナドの値を引数として使用する関数)の正しい構成を可能にするいくつかのプロパティを満たす必要があります。戻り操作は、プレーンタイプから値を取得し、タイプMのモナディックコンテナーに入れます。バインド操作は、逆のプロセスを実行し、コンテナーから元の値を抽出して、パイプライン内の関連する次の関数に渡します。

プログラマーはデータ処理パイプラインを定義するモナド関数を作成します。モナドは、パイプライン内の特定のモナド関数が呼び出される順序を決定し、計算に必要なすべての潜伏作業を管理する再利用可能な動作であるため、フレームワークとして機能します。[3] パイプラインでインターリーブされたバインド演算子と戻り演算子は、各モナド関数が制御を返した後に実行され、モナドによって処理される特定の側面を処理します。

それはそれを非常によく説明していると思います。


12

OOP用語を使用して管理できる最も短い定義を作成するようにします。

ジェネリッククラスCMonadic<T>は、少なくとも以下のメソッドを定義する場合、モナドです。

class CMonadic<T> { 
    static CMonadic<T> create(T t);  // a.k.a., "return" in Haskell
    public CMonadic<U> flatMap<U>(Func<T, CMonadic<U>> f); // a.k.a. "bind" in Haskell
}

次の法則がすべてのタイプTおよびそれらの可能な値tに適用される場合

左のアイデンティティ:

CMonadic<T>.create(t).flatMap(f) == f(t)

正しいアイデンティティ

instance.flatMap(CMonadic<T>.create) == instance

関連性:

instance.flatMap(f).flatMap(g) == instance.flatMap(t => f(t).flatMap(g))

リストモナドには次のものがあります。

List<int>.create(1) --> [1]

リスト[1,2,3]のflatMapは次のように機能します。

intList.flatMap(x => List<int>.makeFromTwoItems(x, x*10)) --> [1,10,2,20,3,30]

IterablesとObservablesは、約束とタスクだけでなく、モナドにすることもできます。

解説

モナドはそれほど複雑ではありません。flatMap関数は、多くの一般的に遭遇するようなものですmap。関数の引数(デリゲートとも呼ばれます)を受け取ります。この引数は、ジェネリッククラスの値を使用して(すぐにまたは後で、0回以上)呼び出すことができます。渡された関数も戻り値を同じ種類のジェネリッククラスにラップすることを期待しています。それを支援するためにcreate、値からそのジェネリッククラスのインスタンスを作成できるコンストラクタを提供します。flatMapの戻り結果も同じタイプの汎用クラスであり、flatMapの1つ以上のアプリケーションの戻り結果に含まれていた同じ値を、以前に含まれていた値にパックすることがよくあります。これにより、flatMapを必要なだけチェーンできます。

intList.flatMap(x => List<int>.makeFromTwo(x, x*10))
       .flatMap(x => x % 3 == 0 
                   ? List<string>.create("x = " + x.toString()) 
                   : List<string>.empty())

たまたま、この種のジェネリッククラスは、膨大な数の基本モデルとして役立ちます。これが(カテゴリ理論の専門用語とともに)モナドが理解または説明するのが難しいように見える理由です。それらは非常に抽象的なものであり、専門化されて初めて明らかに役立つものになります。

たとえば、モナディックコンテナを使用して例外をモデル化できます。各コンテナには、操作の結果または発生したエラーが含まれます。flatMapコールバックのチェーンの次の関数(デリゲート)は、前の関数がコンテナーに値をパックした場合にのみ呼び出されます。それ以外の場合、エラーがパックされていると、エラーは、呼び出されたメソッドを介してエラーハンドラー関数がアタッチされているコンテナーが見つかるまで、チェーンされたコンテナーを通じて伝播し続けます.orElse()(そのようなメソッドは許可される拡張です)。

:関数型言語を使用すると、あらゆる種類のモナディックジェネリッククラスを操作できる関数を作成できます。これが機能するためには、モナド用の汎用インターフェースを作成する必要があります。そのようなインターフェイスをC#で作成することが可能かどうかはわかりませんが、私が知る限りそうではありません。

interface IMonad<T> { 
    static IMonad<T> create(T t); // not allowed
    public IMonad<U> flatMap<U>(Func<T, IMonad<U>> f); // not specific enough,
    // because the function must return the same kind of monad, not just any monad
}

7

モナドがオブジェクト指向で「自然な」解釈を持つかどうかは、モナドに依存します。Javaのような言語では、多分モナドをnullポインターをチェックする言語に変換できるので、失敗した(つまり、HaskellでNothingを生成する)計算は結果としてnullポインターを出力します。状態モナドは、変更可能な変数とその状態を変更するメソッドを作成することによって生成された言語に変換できます。

モナドはエンドファンクターのカテゴリーのモノイドです。

文がまとめる情報は非常に深いです。そして、あなたは命令型言語を使ってモナドで働きます。モナドは「シーケンスされた」ドメイン固有の言語です。これは、モナドを「命令型プログラミング」の数学モデルにするいくつかの興味深い特性を満たします。Haskellを使用すると、さまざまな方法で組み合わせることができる小さな(または大きな)命令型言語を簡単に定義できます。

オブジェクト指向プログラマーは、言語のクラス階層を使用して、コンテキストで呼び出すことができる関数やプロシージャの種類、つまりオブジェクトと呼ばれるものを編成します。異なるモナドを任意の方法で組み合わせることができ、すべてのサブモナドのメソッドをスコープに効果的に「インポート」できる限り、モナドもこのアイデアの抽象概念です。

アーキテクチャ的には、型シグネチャを使用して、値の計算に使用できるコンテキストを明示的に表現します。

この目的でモナド変換子を使用することができ、すべての「標準」モナドの高品質のコレクションがあります。

  • リスト(リストをドメインとして扱うことによる非決定的な計算)
  • 多分(失敗する可能性があるが、報告が重要でない計算)
  • エラー(失敗する可能性があり、例外処理を必要とする計算)
  • リーダー(単純なHaskell関数の構成で表すことができる計算)
  • ライター(順次「レンダリング」/「ロギング」を使用した計算(文字列、htmlなど))
  • 続き(続き)
  • IO(基盤となるコンピューターシステムに依存する計算)
  • 状態(コンテキストに変更可能な値が含まれる計算)

対応するモナド変換子と型クラス。型クラスは、それらのインターフェースを統合することによってモナドを組み合わせる補足的なアプローチを可能にします。その結果、具象モナドはモナドの「種類」の標準インターフェースを実装できます。たとえば、モジュールControl.Monad.StateにはクラスMonadState smが含まれており、(State s)は次の形式のインスタンスです。

instance MonadState s (State s) where
    put = ...
    get = ...

長い話は、モナドは値に「コンテキスト」を付加するファンクタであり、モナドに値を注入する方法があり、それにアタッチされたコンテキストに関して値を評価する方法があります。制限された方法で。

そう:

return :: a -> m a

タイプaの値をタイプm aのモナド「アクション」に注入する関数です。

(>>=) :: m a -> (a -> m b) -> m b

モナドアクションを実行し、その結果を評価し、結果に関数を適用する関数です。(>> =)の優れた点は、結果が同じモナドにあることです。つまり、m >> = fでは、(>> =)はmから結果を引き出してfにバインドし、結果がモナドにあるようにします。(または、(>> =)はfをmにプルして結果に適用すると言うことができます。)結果として、f :: a-> mb、およびg :: b-> mcがある場合、 「シーケンス」アクション:

m >>= f >>= g

または、「do notation」を使用します

do x <- m
   y <- f x
   g y

(>>)のタイプは照らしている可能性があります。です

(>>) :: m a -> m b -> m b

これは、Cのような手続き型言語の(;)演算子に対応しています。

m = do x <- someQuery
       someAction x
       theNextAction
       andSoOn

数学的および哲学的ロジックには、モナディズムで「自然に」モデル化されたフレームとモデルがあります。解釈とは、モデルのドメインを調べ、命題(または一般化の下での公式)の真理値(または一般化)を計算する関数です。必要に応じたモーダルロジックでは、「すべての可能な世界」でそれが真である場合、つまりすべての許容可能なドメインに関して真である場合、命題が必要であると言えます。これは、命題のための言語のモデルは、そのドメインが別個のモデル(考えられる各世界に対応するモデル)のコレクションで構成されるモデルとして具体化できることを意味します。すべてのモナドには、レイヤーを平坦化する「結合」という名前のメソッドがあります。これは、結果がモナドアクションであるすべてのモナドアクションをモナドに埋め込むことができることを意味します。

join :: m (m a) -> m a

さらに重要なのは、モナドが「レイヤースタッキング」操作の下で閉じられていることです。これがモナドトランスフォーマーのしくみです。モナドは次のような型に「結合のような」メソッドを提供することでモナドを結合します

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

(MaybeT m)のアクションをmのアクションに変換し、レイヤーを効果的に折りたたむことができるようにします。この場合、runMaybeT :: MaybeT ma-> m(Maybe a)は、結合のようなメソッドです。(MaybeT m)はモナドであり、MaybeT :: m(Maybea)-> MaybeT maは事実上、mの新しいタイプのモナドアクションのコンストラクタです。

ファンクタのフリーモナドは、fをスタックすることによって生成されるモナドであり、fのコンストラクタのすべてのシーケンスはフリーモナドの要素(より正確には、コンストラクタのシーケンスのツリーと同じ形状の何か)であることを意味します。 f)。フリーモナドは、最小限のボイラープレートで柔軟なモナドを構築するための便利なテクニックです。Haskellプログラムでは、無料のモナドを使用して、「安全性の高いシステムプログラミング」のための単純なモナドを定義し、型の安全性を維持できます(型とその宣言を使用しているだけです。実装は、コンビネータを使用すると簡単です)。

data RandomF r a = GetRandom (r -> a) deriving Functor
type Random r a = Free (RandomF r) a


type RandomT m a = Random (m a) (m a) -- model randomness in a monad by computing random monad elements.
getRandom     :: Random r r
runRandomIO   :: Random r a -> IO a (use some kind of IO-based backend to run)
runRandomIO'  :: Random r a -> IO a (use some other kind of IO-based backend)
runRandomList :: Random r a -> [a]  (some kind of list-based backend (for pseudo-randoms))

すべてのモナディック計算は少なくとも自明に「実行」する必要があるため、モナディズムは「インタプリタ」または「コマンド」パターンと呼ばれるものの基本アーキテクチャであり、最も明確な形式に抽象化されています。(ランタイムシステムはIOモナドを実行し、Haskellプログラムへのエントリポイントです。IOは、IOアクションを順番に実行することにより、残りの計算を「駆動」します)。

結合のタイプは、モナドが内部関数のカテゴリのモノイドであるというステートメントを取得する場所でもあります。結合は通常、そのタイプのおかげで、理論的な目的にとってより重要です。しかし、型を理解することはモナドを理解することを意味します。結合およびモナド変換子の結合のようなタイプは、関数構成という意味で、実質的に内部関数の構成です。それをHaskellのような疑似言語に入れるには、

Foo :: m(ma)<->(m。m)a


3

モナドは関数の配列です

(Pst:関数の配列は単なる計算です)。

実際には、真の配列(1つのセル配列に1つの関数)の代わりに、それらの関数が別の関数>> =によってチェーンされています。>> =は、関数iからの結果をフィード関数i + 1に適合させ、それらの間で計算を実行したり、関数i + 1を呼び出さないようにすることもできます。

ここで使用されるタイプは、「コンテキスト付きタイプ」です。これは、「タグ」付きの値です。チェーンされる関数は、「裸の値」を取り、タグ付けされた結果を返す必要があります。>> =の義務の1つは、そのコンテキストからネイキッドな値を抽出することです。また、ネイキッドな値を取得してタグを付ける関数「return」もあります。

多分の例。これを使用して、計算を行う単純な整数を格納します。

-- a * b
multiply :: Int -> Int -> Maybe Int
multiply a b = return  (a*b)

-- divideBy 5 100 = 100 / 5
divideBy :: Int -> Int -> Maybe Int
divideBy 0 _ = Nothing -- dividing by 0 gives NOTHING
divideBy denom num = return (quot num denom) -- quotient of num / denom

-- tagged value
val1 = Just 160 

-- array of functions feeded with val1
array1 = val1 >>= divideBy 2  >>= multiply 3 >>= divideBy  4 >>= multiply 3

-- array of funcionts created with the do notation
-- equals array1 but for the feeded val1
array2 :: Int -> Maybe Int
array2 n = do
       v <- divideBy 2  n
       v <- multiply 3 v
       v <- divideBy 4 v
       v <- multiply 3 v
       return v

-- array of functions, 
-- the first >>= performs 160 / 0, returning Nothing
-- the second >>= has to perform Nothing >>= multiply 3 ....
-- and simply returns Nothing without calling multiply 3 ....
array3 = val1 >>= divideBy 0  >>= multiply 3 >>= divideBy  4 >>= multiply 3

main = do
     print array1
     print (array2 160)
     print array3

モナドがヘルパー操作のある関数の配列であることを示すために、実際の関数の配列を使用して、上記の例と同等のものを検討してください。

type MyMonad = [Int -> Maybe Int] -- my monad as a real array of functions

myArray1 = [divideBy 2, multiply 3, divideBy 4, multiply 3]

-- function for the machinery of executing each function i with the result provided by function i-1
runMyMonad :: Maybe Int -> MyMonad -> Maybe Int
runMyMonad val [] = val
runMyMonad Nothing _ = Nothing
runMyMonad (Just val) (f:fs) = runMyMonad (f val) fs

そしてそれは次のように使用されます:

print (runMyMonad (Just 160) myArray1)

1
すっきり!したがって、bindは、コンテキストを持つ入力で、コンテキストを持つ関数の配列を順番に評価する方法にすぎません:)
Musa Al-hassy

>>=オペレーターです
-user2418306

1
「関数の配列」のアナロジーはあまり明確ではないと思います。Ifは\x -> x >>= k >>= l >>= m関数の配列である、そうであるh . g . fすべてのモナドを伴いません、。
16

私たちはそれを言うことができます モナディック、アプリケーション、プレーンのいずれであっても、ファンクタ「装飾されたアプリケーション」に関するものであるます。'applicative'は連鎖を追加し、 'monad'は依存関係を追加します(つまり、前の計算ステップの結果に応じて次の計算ステップを作成します)。
Will Ness、

3

オブジェクト指向の用語では、モナドは流暢なコンテナです。

最小要件は、class <A> SomethingコンストラクタSomething(A a)と少なくとも1つのメソッドをサポートするの定義ですSomething<B> flatMap(Function<A, Something<B>>)

間違いなく、モナドクラスにSomething<B> work()、クラスのルールを保持するシグネチャを持つメソッドがあるかどうかも考慮されます。コンパイラはコンパイル時にflatMapでベイクします。

モナドはなぜ有用なのですか?これは、セマンティクスを保持するチェーン可能な操作を可能にするコンテナーであるためです。例えば、Optional<?>用isPresentのセマンティクスを保持しOptional<String>Optional<Integer>Optional<MyClass>、など

大まかな例として、

Something<Integer> i = new Something("a")
  .flatMap(doOneThing)
  .flatMap(doAnother)
  .flatMap(toInt)

文字列で始まり、整数で終わることに注意してください。かなりクール。

OOでは少し手を振る必要があるかもしれませんが、Somethingの別のサブクラスを返すSomethingのメソッドは、元の型のコンテナーを返すコンテナー関数の基準を満たしています。

これがセマンティクスを維持する方法です。つまり、コンテナーの意味と操作は変更されず、コンテナー内のオブジェクトをラップして拡張するだけです。


2

典型的な使用法のモナドは、手続き型プログラミングの例外処理メカニズムと機能的に同等です。

最新の手続き型言語では、一連のステートメントの周囲に例外ハンドラーを配置します。ステートメントのいずれかが例外をスローする可能性があります。ステートメントのいずれかが例外をスローした場合、一連のステートメントの通常の実行は停止し、例外ハンドラに転送されます。

ただし、関数型プログラミング言語は、「goto」のような性質のため、例外処理機能を哲学的に回避しています。関数型プログラミングの観点では、関数にはプログラムフローを中断させる例外のような「副作用」があってはなりません。

実際には、主にI / Oが原因で、現実の世界で副作用を排除することはできません。関数型プログラミングのモナドを使用して、一連の連鎖関数呼び出し(予期しない結果を生成する可能性がある)を取り、予期しない結果をカプセル化されたデータに変換して、残りの関数呼び出しを安全に通過できるようにします。

制御のフローは保持されますが、予期しないイベントは安全にカプセル化されて処理されます。


2

Marvelのケーススタディを使用した簡単なモナドの説明はこちらです。

モナドは、効果的な従属関数の順序付けに使用される抽象化です。ここで効果的とは、型コンストラクターと呼ばれるOption [A]のようにF [A]の形式で型を返すことを意味します。これを2つの簡単なステップで見てみましょう

  1. 以下の関数構成は推移的です。したがって、AからCIに移動するには、A => BおよびB => Cを構成できます。
 A => C   =   A => B  andThen  B => C

ここに画像の説明を入力してください

  1. ただし、関数がOption [A]、つまりA => F [B]のようなエフェクトタイプを返す場合、コンポジションはBに移動するように機能しませんが、A => Bが必要ですが、A => F [B]があります。
    ここに画像の説明を入力してください

    F [A]を返すこれらの関数を融合する方法を知っている特別な演算子「バインド」が必要です。

 A => F[C]   =   A => F[B]  bind  B => F[C]

「バインド」機能は、特定のために定義されるF

また、特定のFに対して定義され、任意のAに対してタイプA => F [A]の「return」もあります。モナドになるために、Fはこれらの2つの関数を定義する必要があります。

したがって、純粋な関数A => Bから効果的な関数A => F [B]を構築できます。

 A => F[B]   =   A => B  andThen  return

ただし、特定のFは、ユーザーが自分で(純粋な言語で)定義できないような、そのようなタイプの独自の不透明な「組み込み」特殊関数を定義することもできます。

  • "ランダム"(範囲=>ランダム[整数]
  • "print"(文字列=> IO [()]
  • 「やってみよう…キャッチ」など

2

理論的に完全ではないかもしれないモナドについての私の理解を共有しています。モナドは、コンテキストの伝播に関するものです。モナドは、いくつかのデータ(またはデータ型)のコンテキストを定義し、そのコンテキストがデータとともに処理パイプライン全体にどのように運ばれるかを定義します。そして、コンテキスト伝播の定義は、ほとんどの場合、(同じタイプの)複数のコンテキストをマージする方法を定義することです。モナドを使用すると、これらのコンテキストが誤ってデータから削除されないようにすることも意味します。一方、他のコンテキストレスデータは、新規または既存のコンテキストに取り込むことができます。次に、この単純な概念を使用して、プログラムのコンパイル時の正確さを保証できます。



1

「モナドとは」への私の答えを見てください。

それは動機付けの例から始まり、例を通して働き、モナドの例を導き出し、正式に「モナド」を定義します。

関数型プログラミングの知識がないことを前提とし、疑似コードを使用して function(argument) := expression、可能な限り最も単純な式の構文でます。

このC ++プログラムは、疑似コードモナドの実装です。(参考:Mは型コンストラクターでfeedあり、は「バインド」操作でありwrap、「戻り」操作です。)

#include <iostream>
#include <string>

template <class A> class M
{
public:
    A val;
    std::string messages;
};

template <class A, class B>
M<B> feed(M<B> (*f)(A), M<A> x)
{
    M<B> m = f(x.val);
    m.messages = x.messages + m.messages;
    return m;
}

template <class A>
M<A> wrap(A x)
{
    M<A> m;
    m.val = x;
    m.messages = "";
    return m;
}

class T {};
class U {};
class V {};

M<U> g(V x)
{
    M<U> m;
    m.messages = "called g.\n";
    return m;
}

M<T> f(U x)
{
    M<T> m;
    m.messages = "called f.\n";
    return m;
}

int main()
{
    V x;
    M<T> m = feed(f, feed(g, wrap(x)));
    std::cout << m.messages;
}

0

実用的な観点(これまでの多くの回答と関連記事で述べられていることを要約する)から、モナドの基本的な「目的」(または有用性)の1つは、再帰的なメソッド呼び出しで暗黙的な依存関係を利用することであるように思えます別名関数構成(つまり、f1がf2を呼び出し、f3を呼び出す場合、f1の前にf2の前にf3を評価する必要があります)は、特に遅延評価モデルのコンテキスト(つまり、単純なシーケンスとしての順次構成)で自然に順次構成を表す、たとえばCの「f3(); f2(); f1();」-f3、f2、f1が実際には何も返さない場合を考えると、トリックは特に明白です[それらのチェーンはf1(f2(f3))として連鎖します)人工的なものであり、純粋にシーケンスを作成することを目的としています])。

これは、副作用が関係している場合、つまり一部の状態が変更された場合に特に関連します(f1、f2、f3に副作用がない場合、それらが評価される順序は関係ありません。これは、pureの優れたプロパティです。これらの計算を並列化できるようにするための関数型言語など)。より純粋な関数であるほど良いです。

その狭い観点から、モナドは遅延評価を好む言語(絶対に必要な場合にのみ物事を評価し、コードの表示に依存しない順序に従う)の構文糖と見なすことができ、シーケンシャルコンポジションを表す他の方法。最終的な結果は、「純粋でない」(つまり、副作用がある)コードのセクションは、命令型の方法で自然に提示でき、しかも純粋な関数(副作用のない)から明確に分離できるということです。怠惰に評価した。

ここで警告されているように、これは1つの側面にすぎません。


0

私が考えることができる最も簡単な説明は、モナドは装飾された結果(別名Kleisli合成)で関数を構成する方法であるということです。「embelished」関数には、a -> (b, smth)どこにa、そしてbタイプ(思うありIntBool互いに異なるかもしれない)、必ずしもそうではない-とsmth「コンテキスト」または「エンベリッシュメント」です。

このタイプの関数はa -> m b、次の場所にも記述できます。m、「埋め込み」と同等のsmthます。したがって、これらはコンテキストで値を返す関数です(アクションをログに記録する関数(smthログメッセージはです)。または、入出力を実行する関数とその結果は、IOアクションの結果によって異なります)。

モナドは、そのような関数を作成する方法を実装者に指示するインターフェース(「タイプクラス」)です。実装者は合成関数を定義する必要があります(a -> m b) -> (b -> m c) -> (a -> m c)m実装者は、インターフェースを実装したいすべての型のあります(これはKleisli構成です)。

したがって、s (Int, String)の計算結果を表すタプルタイプがあり、Intそのアクションもログに記録されます。これ(_, String)は、「埋め込み」-アクションのログ-と2つの関数increment :: Int -> (Int, String)twoTimes :: Int -> (Int, String)我々は、機能取得するincrementThenDouble :: Int -> (Int, String)組成物でありますログも考慮に入れる2つの関数の。

与えられた例では、2つの関数のモナド実装は整数値2 incrementThenDouble 2(これはに等しいtwoTimes (increment 2))に適用され(6, " Adding 1. Doubling 3.")、中間結果increment 2は次(3, " Adding 1.")twoTimes 3等しいか等しい(6, " Doubling 3.")

このクライスリ合成関数から、通常のモナド関数を導出できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.