1598

最近Haskellを簡単に見てきましたが、モナドが本質的に何であるかについて、簡潔で簡潔で実用的な説明は何でしょうか?

私が出くわしたほとんどの説明は、かなりアクセスできず、実用的な詳細が不足していることがわかりました。

4

47 に答える 47

1221

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

連鎖操作のパターンです。オブジェクト指向言語のメソッド連鎖に少し似ていますが、メカニズムは少し異なります。

このパターンは主に関数型言語 (特に広くモナドを使用する Haskell) で使用されますが、高階関数 (つまり、他の関数を引数として取ることができる関数) をサポートする任意の言語で使用できます。

JavaScript の配列はこのパターンをサポートしているので、それを最初の例として使用しましょう。

パターンの要点は、Array引数として関数を取るメソッドを持つ型 (この場合) があることです。指定された操作は、同じ型のインスタンスを返さなければなりません (つまり、 を返しますArray)。

最初に、モナド パターンを使用しないメソッド チェーンの例を示します。

[1,2,3].map(x => x + 1)

結果は[2,3,4]です。引数として提供している関数は配列ではなく数値を返すため、コードはモナドパターンに準拠していません。モナド形式の同じロジックは次のようになります。

[1,2,3].flatMap(x => [x + 1])

ここでは、 を返す操作を提供するArrayため、パターンに準拠しています。このflatMapメソッドは、配列内のすべての要素に対して提供された関数を実行します。各呼び出しの結果として (単一の値ではなく) 配列を期待しますが、配列の結果セットを単一の配列にマージします。したがって、最終結果は同じで、 array [2,3,4].

map(またはのようなメソッドに提供される関数の引数はflatMap、JavaScript では「コールバック」と呼ばれることがよくあります。より一般的なので、「操作」と呼びます。)

複数の操作を (従来の方法で) 連鎖させた場合:

[1,2,3].map(a => a + 1).filter(b => b != 3)

配列の結果[2,4]

モナド形式での同じ連鎖:

[1,2,3].flatMap(a => [a + 1]).flatMap(b => b != 3 ? [b] : [])

同じ結果、 array が得られ[2,4]ます。

モナド形式が非モナド形式よりかなり醜いことにすぐに気付くでしょう! これは、モナドが必ずしも「良い」ものではないことを示しています。それらは、有益な場合もあればそうでない場合もあるパターンです。

モナド パターンは別の方法で組み合わせることができることに注意してください。

[1,2,3].flatMap(a => [a + 1].flatMap(b => b != 3 ? [b] : []))

ここでは、バインドはチェーンではなくネストされていますが、結果は同じです。後で見るように、これはモナドの重要な性質です。これは、組み合わせた 2 つの操作を 1 つの操作と同じように扱うことができることを意味します。

この操作では、さまざまな要素タイプの配列を返すことができます。たとえば、数値の配列を文字列の配列などに変換します。それがまだ配列である限り。

これは、Typescript 表記法を使用してもう少し正式に記述できます。配列の型は ですArray<T>。ここTで、 は配列内の要素の型です。このメソッドflatMap()は、型の関数引数を取りT => Array<U>Array<U>.

一般化すると、モナドは、型Foo<Bar>の関数引数を取り、Bar => Foo<Baz>を返す「バインド」メソッドを持つ任意の型Foo<Baz>です。

これは、モナドとは何かに答えます。この回答の残りの部分では、モナドを適切にサポートするHaskellのような言語でモナドが有用なパターンになる理由を例を通して説明しようとします。

Haskell と Do 記法

マップ/フィルターの例を Haskell に直接変換するには、次flatMap>>=演算子に置き換えます。

[1,2,3] >>= \a -> [a+1] >>= \b -> if b == 3 then [] else [b] 

>>=演算子は Haskell の bind 関数です。オペランドがリストの場合は JavaScriptと同じflatMapですが、他の型では異なる意味でオーバーロードされます。

doしかし、Haskell には、バインド演算子を完全に隠す -blockというモナド式専用の構文もあります。

 do a <- [1,2,3] 
    b <- [a+1] 
    if b == 3 then [] else [b] 

これにより、「配管」が非表示になり、各ステップで適用される実際の操作に集中できます。

-block では、do各行が操作です。制約は、ブロック内のすべての操作が同じ型を返さなければならないことを引き続き保持します。最初の式はリストであるため、他の操作もリストを返さなければなりません。

戻る矢印<-は割り当てのように見えますが、これはバインドで渡されるパラメーターであることに注意してください。したがって、右側の式が整数のリストの場合、左側の変数は単一の整数になりますが、リスト内の整数ごとに実行されます。

例: 安全なナビゲーション (Maybe タイプ)

リストについては十分なので、モナド パターンが他の型にどのように役立つか見てみましょう。

一部の関数は、常に有効な値を返すとは限りません。Haskell では、これは -type で表されます。これは、 または のいずれかMaybeのオプションです。Just valueNothing

常に有効な値を返す操作の連鎖はもちろん簡単です。

streetName = getStreetName (getAddress (getUser 17)) 

しかし、いずれかの関数が を返すことができるとしたらNothing? 各結果を個別にチェックし、そうでない場合にのみ値を次の関数に渡す必要がありますNothing

case getUser 17 of
      Nothing -> Nothing 
      Just user ->
         case getAddress user of
            Nothing -> Nothing 
            Just address ->
              getStreetName address

かなりの繰り返しチェック!チェーンがもっと長い場合を想像してみてください。Haskell は のモナドパターンでこれを解決しますMaybe:

do
  user <- getUser 17
  addr <- getAddress user
  getStreetName addr

このdo-block は、型の bind-function を呼び出しますMaybe(最初の式の結果が であるためMaybe)。bind-function は、値が の場合にのみ次の操作を実行します。それ以外の場合はJust value、単に渡すだけNothingです。

ここでは、コードの繰り返しを避けるためにモナドパターンが使用されています。これは、他の言語がマクロを使用して構文を単純化する方法と似ていますが、マクロは同じ目的を非常に異なる方法で達成します。

Haskell のモナド パターンとモナドに適した構文の組み合わせにより、よりクリーンなコードになることに注意してください。モナドに対する特別な構文サポートのない JavaScript のような言語では、モナド パターンがこの場合のコードを単純化できるとは思えません。

可変状態

Haskell は可変状態をサポートしていません。すべての変数は定数であり、すべての値は不変です。ただし、State型を使用して、変更可能な状態でプログラミングをエミュレートできます。

add2 :: State Integer Integer
add2 = do
        -- add 1 to state
         x <- get
         put (x + 1)
         -- increment in another way
         modify (+1)
         -- return state
         get


evalState add2 7
=> 9

このadd2関数は、初期状態として 7 で評価されるモナド チェーンを構築します。

明らかに、これは Haskell でのみ意味をなすものです。他の言語では、すぐに変更可能な状態がサポートされます。Haskell は通常、言語機能に「オプトイン」されています。必要なときに変更可能な状態を有効にすると、型システムによって効果が明示的になります。IO はこの別の例です。

IO

このIO型は、「不純な」関数の連鎖と実行に使用されます。

他の実用的な言語と同様に、Haskell には、外の世界とやり取りする一連の組み込み関数があります:putStrLineなどreadLine。これらの関数は、副作用を引き起こすか、非決定論的な結果をもたらすため、「不純」と呼ばれます。時間を取得するような単純なものでさえ、結果が非​​決定論的であるため、不純と見なされます。同じ引数で 2 回呼び出すと、異なる値が返される可能性があります。

純粋な関数は決定論的です。その結果は、渡された引数に純粋に依存し、値を返す以外に環境に副作用はありません。

Haskell は、純粋関数の使用を強く推奨しています。これは、この言語の主要なセールス ポイントです。残念なことに、純粋主義者にとっては、有用なことを行うにはいくつかの不純な関数が必要です。Haskell の妥協点は、純粋な関数と純粋でない関数を明確に分離し、純粋な関数が純粋でない関数を直接的または間接的に実行できないことを保証することです。

これは、すべての非純粋な関数にIO型を与えることによって保証されます。Haskell プログラムのエントリ ポイントはmain、型を持つ関数IOであるため、トップ レベルで非純粋な関数を実行できます。

しかし、純粋な関数が非純粋な関数を実行するのを言語はどのように防いでいるのでしょうか? これは、Haskell の怠惰な性質によるものです。関数は、その出力が他の関数によって消費された場合にのみ実行されます。IOしかし、値を に代入する以外に値を消費する方法はありませんmain。したがって、関数が不純な関数を実行したい場合は、接続されmainIO型が必要です。

IO 操作にモナド連鎖を使用すると、命令型言語のステートメントと同様に、それらが線形で予測可能な順序で実行されることも保証されます。

これで、ほとんどの人が Haskell で書く最初のプログラムにたどり着きます。

main :: IO ()
main = do 
        putStrLn ”Hello World”

操作が 1 つしかなく、バインドするものが何もない場合、このdoキーワードは不要ですが、一貫性を保つためにそのままにしておきます。

()タイプは「空」を意味します。この特別な戻り値の型は、副作用のために呼び出される IO 関数にのみ役立ちます。

より長い例:

main = do
    putStrLn "What is your name?"
    name <- getLine
    putStrLn "hello" ++ name

これにより一連の操作が構築され、関数にIO割り当てられているため、実行されます。main

と比較するIOMaybe、モナド パターンの汎用性が示されます。の場合Maybe、条件付きロジックをバインディング関数に移動することにより、コードの繰り返しを避けるためにパターンが使用されます。の場合、パターンを使用して、型IOのすべての操作が順序付けられ、操作が純粋な関数に「漏れる」ことがないようにします。IOIO

まとめ

私の主観的な意見では、モナド パターンは、パターンのサポートが組み込まれている言語でのみ真に価値があります。そうしないと、過度に複雑なコードになるだけです。しかし、Haskell (およびいくつかの他の言語) には、面倒な部分を隠すサポートが組み込まれており、そのパターンをさまざまな便利なことに使用できます。お気に入り:

  • コードの繰り返しを避ける ( Maybe)
  • プログラムの区切られた領域の変更可能な状態や例外などの言語機能を追加します。
  • 厄介なものを良いものから分離する ( IO)
  • 組み込みのドメイン固有言語 ( Parser)
  • 言語に GOTO を追加します。
于 2008-10-11T15:31:36.807 に答える
778

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

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

本質的には、実行ステップを記述し、「バインド関数」でそれらをリンクしています。(Haskell では、 という名前が付けられてい>>=ます。) bind 演算子の呼び出しを自分で作成することも、コンパイラにそれらの関数呼び出しを挿入させる構文シュガーを使用することもできます。いずれにせよ、各ステップはこの bind 関数の呼び出しによって区切られます。

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

難しそうに聞こえませんよね?しかし、モナドには複数の種類があります。なんで?どのように?

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

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

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

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

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

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

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

  • 「余分なデータ」を差し替えできるようにすることができます。これにより、実際に破壊的な更新を行うことなく、破壊的な更新をシミュレートできます。( State モナドとその従兄弟であるWriter モナド。)

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

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

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

モナドを使えば、これ以上のことが可能です。もちろん、これらすべてはモナドなしでも完全に可能です。モナドを使用するのは非常に簡単です。

于 2012-04-20T11:26:46.200 に答える
205

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

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

data Wrapped a = Wrap a

定義したものをラップするには

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

アンラップせずに操作を実行するには、 function があるとします。これを実行して、その関数を持ち上げてラップされた値に作用させる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でしか定義できません。したがって、モナドを定義するときは、その型 (ここでは ) を与えてから、その操作がどのように機能するかを言います。bindreturnWrapped areturnbind

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

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

モナドを理解することに関しては、あまり心配する必要はありません。それらについて興味のあることを読んで、すぐに理解できなくても心配しないでください。その場合は、Haskell のような言語に飛び込むだけです。モナドは、練習によって理解が脳に浸透するものの 1 つです。ある日突然、モナドを理解していることに気づきます。

于 2008-09-16T12:26:25.373 に答える
178

しかし、あなたはモナドを発明することができたでしょう!

sigfpeは言う:

しかし、これらはすべて、説明が必要な秘教的なものとしてモナドを紹介しています。しかし、私が主張したいのは、それらはまったく難解ではないということです。実際、関数型プログラミングでさまざまな問題に直面すると、容赦なく特定の解決策に導かれることになります。これらはすべてモナドの例です。実際、まだ発明していないのであれば、今すぐ発明してもらいたいと思います。次に、これらのソリューションのすべてが実際には偽装された同じソリューションであることに気付くのは小さなステップです。そして、これを読んだ後、あなたはあなたがすでに発明したものとしてあなたが見るすべてを認識するので、モナドに関する他の文書を理解するためのより良い立場にいるかもしれません。

モナドが解決しようとする問題の多くは、副作用の問題に関連しています。それで、それらから始めましょう。(モナドを使用すると、副作用を処理するだけでなく、特に多くの種類のコンテナオブジェクトをモナドと見なすことができます。モナドの概要の中には、これら2つの異なるモナドの使用法を調整して、1つまたは他。)

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

于 2008-08-05T16:28:41.483 に答える
89

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

Haskell 表記では、モナド インターフェイスは次のように記述されます。

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

これらの操作は特定の「法則」に従うことになっていますが、それはそれほど重要ではありません: 「法則」は、操作の賢明な実装が振る舞うべき方法を成文化するだけです (基本的に、>>=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

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

モナドとは何か、なぜモナドが有用なのかを理解するには、自明ではない Haskell コードを書いて遊んでみる必要があります。

于 2008-09-05T02:50:54.807 に答える
82

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

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

ファンクターTは、高階関数が存在する任意の型構築であり、それを呼び出します。これmapは、型a -> b(任意の 2 つの型aandが与えられたb) の関数を function に変換しますT a -> T b。この関数は、次の式がすべてのand (Haskell 表記)mapに対して true を返すように、同一性と構成の法則にも従う必要があります。pq

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

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

モナドは基本的に、 、 型の 、および( 、、またはと呼ばれることもある)型のT2 つの追加メソッドを持つ単なるファンクタです。Haskell のリストの場合:joinT (T a) -> T aunitreturnforkpurea -> T a

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

なぜそれが役立つのですか?mapたとえば、リストを返す関数を使用してリストを処理できるためです。Join結果のリストのリストを取得し、それらを連結します。Listこれが可能であるため、モナドです。

を行う関数を書くことができmapますjoin。この関数はbind、 またはflatMap、 または(>>=)、または と呼ばれ(=<<)ます。これは通常、Haskell でモナド インスタンスを指定する方法です。

モナドは特定の法則を満たさなければなりません。つまり、それjoinは結合的でなければなりません。xこれは、タイプの値がある場合[[[a]]]join (join x)等しい必要があることを意味しますjoin (map join x)。そして、そのようなpureためのアイデンティティでなければなりません。joinjoin (pure x) == x

于 2008-09-27T06:36:19.270 に答える
53
于 2008-09-17T19:04:21.120 に答える
46

多くの努力の末、ようやくモナドを理解できたと思います。圧倒的に多くの投票を得た回答に対する私自身の長い批評を読み直した後、私はこの説明を提供します.

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

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

最初のコメントで指摘したように、あまりにも多くのモナドの説明が、質問 2 または質問 1 を適切にカバーする前に、質問 3 に巻き込まれています。

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

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%!");

これは確かにそれを書くためのより便利な方法です。しかし、どうすればそれを行うことができますか?

モナドとは?

定義した型 ( など) と、その型で動作するように特別に設計された一連の関数があれば、 (bind)consolestateなどの演算子を定義することで、これらのパッケージ全体を「モナド」に変えることができます。:左側に戻り値をフィードし、右側に関数パラメーターをフィードし、lift通常の関数をその特定の種類のバインド オペレーターで動作する関数に変換するオペレーターをフィードします。

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

その詳細に飛び込むのはかなり自由だと思われる他の回答を参照してください。

于 2010-11-06T09:27:46.160 に答える
43

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

モナドは、構成中に入力を前処理するために構成関数 を使用して、いくつかの入力シナリオの処理を外部化する関数構成手法ですbind

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

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

この設計は、関連する状態をより簡単に調べることができるように入力を再構築することで改善できます。yそのため、値は単純にではなく、たとえば有効性の概念が含まれている場合Mbなどになる可能性があります。(is_OK, b)y

たとえば、入力が数値のみの可能性がある場合、厳密に数値を含むかどうかを指定できる文字列を返すのではなく、型を に再構築してbool、有効な数値とタプル内の数値 ( など) の存在を示すことができますbool * float。構成された関数は、数値が存在するかどうかを判断するために入力文字列を解析する必要がなくなり、単にboolタプルの一部を調べることができました。

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

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

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

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

(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)

Associativityがいつ適用されるbindかに関係なく、評価の順序を保持することを意味します。bindつまり、上記の定義では、括弧で囲まれたandAssociativityの強制早期評価は、を完了するために必要な関数になるだけです。したがって、その値が に適用され、その結果が に適用される前に、 の評価を決定する必要があります。bindingfgMabindMafg

于 2014-08-08T02:19:30.523 に答える
37

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

「たぶんモナド」は、基本的に、Visual Basic / C# の「null 許容型」に相当します。Nullable でない型 "T" を取り、それを "Nullable<T>" に変換し、Nullable<T> ですべての二項演算子が何を意味するかを定義します。

副作用も同様に表されます。関数の戻り値とともに副作用の説明を保持する構造体が作成されます。「持ち上げられた」操作は、関数間で値が渡されるときに副作用をコピーします。

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

  1. モナドには、できることに制限があります (詳細については、定義を参照してください)。
  2. これらの制限は、関連する 3 つの操作があるという事実と共に、数学のあいまいな分野である圏論でモナドと呼ばれるものの構造に準拠しています。
  3. それらは「純粋な」関数型言語の支持者によって設計されました
  4. 数学のあいまいな分野のような純粋な関数型言語の支持者
  5. 数学は曖昧であり、モナドはプログラミングの特定のスタイルに関連付けられているため、人々はモナドという言葉を一種の秘密の握手として使用する傾向があります. このため、誰もより良い名前に投資することを気にしませんでした.
于 2008-08-31T19:19:50.603 に答える
35

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

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

DR MacIver がTell us why your language sucksで述べているように:

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

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

あなたはMaybeモナドを理解していると言いますか?よし、向かってるぞ。他のモナドを使い始めるだけで、遅かれ早かれモナドが一般的に何であるかを理解できるようになります。

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

また、驚くべきことに、モナドの最も優れた入門書の 1 つは、モナドを紹介した初期の学術論文の 1 つである Philip Wadler のMonads for function programmingです。そこにある人工的なチュートリアルの多くとは異なり、実際には実用的で自明ではない動機付けの例があります.

于 2008-09-07T01:02:03.303 に答える
24

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

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

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

したがって、モナドは単なる制御フロー構造です。モナドを置き換えるより適切な表現は、「制御型」です。

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

たとえば、「if」モナドは次のようになります。

if( clause ) then block

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

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

もちろん、データ構造を繰り返し処理したり走査したりするのと同じ方法で、モナドを評価することができます。

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

于 2009-05-20T00:30:42.863 に答える
15

私のお気に入りの Monad チュートリアル:

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

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

@Stu:モナドのポイントは、(通常は)シーケンシャルセマンティクスを他の純粋なコードに追加できるようにすることです。(Monad Transformer を使用して) モナドを作成することもでき、たとえば、エラー処理、共有状態、およびログを使用した解析など、より興味深く複雑な組み合わせセマンティクスを得ることができます。これはすべて純粋なコードで可能であり、モナドを使用すると、それを抽象化してモジュラー ライブラリで再利用できます (プログラミングでは常に優れています)。また、命令的であるように見せるための便利な構文を提供します。

Haskell にはすでに演算子のオーバーロードがあります[1]: Java や C# でインターフェイスを使用するのと同じように型クラスを使用しますが、Haskell ではたまたま + && や > などの英数字以外のトークンを中置識別子として使用できます。「セミコロンのオーバーロード」を意味する場合、それはあなたの見方でのみ演算子のオーバーロードです[2]。「セミコロンをオーバーロードする」ことは黒魔術のように聞こえますが (進取の気性に富んだ Perl ハッカーがこの考えを思いついています)、要点はモナドがなければセミコロンは存在しないということです。なぜなら、純粋に機能的なコードは明示的な順序付けを必要とせず、許可もしないからです。

これはすべて、必要以上に複雑に聞こえます。sigfpe の記事は非常にクールですが、Haskell を使用して説明しています。これは、Haskell を理解して Monad を理解し、Monads を理解して Haskell を理解するという鶏と卵の問題を解決できていません。

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

[2] モナド アクションを連鎖させる演算子は >>= (「バインド」と発音) であるため、これも単純化しすぎていますが、ブレースとセミコロン、および/またはインデントと改行を使用できる構文シュガー (「do」) があります。

于 2008-08-30T06:50:43.160 に答える
10

私はまだモナドに慣れていませんが、読んで本当に良かったと感じたリンクを共有したいと思いました (WITH PICTURES!!): http://www.matusiak.eu/numerodix/blog/2012/3/11/ monads-for-the-layman/ (所属なし)

基本的に、私がこの記事から得た暖かくあいまいな概念は、モナドは基本的に、異種の関数を構成可能な方法で動作させるアダプターであるという概念でした。つまり、一貫性のない戻り値を心配することなく、複数の関数をつなぎ合わせて組み合わせて一致させることができます。種類など。したがって、BIND 関数は、これらのアダプターを作成しようとしているときに、リンゴとリンゴ、オレンジとオレンジを保持する役割を果たします。また、LIFT 関数は、「下位レベル」の関数を取得し、BIND 関数で動作するように「アップグレード」し、構成可能にすることも担当します。

正しく理解できていることを願っています。さらに重要なことは、この記事がモナドについて有効な見解を持っていることを願っています。少なくとも、この記事は、モナドについてもっと学びたいという私の欲求を刺激するのに役立ちました。

于 2014-02-27T19:38:44.067 に答える
9

最近、私はモナドについて別の方法で考えています。私はそれらを数学的な方法で実行順序を抽象化することとして考えてきました。これにより、新しい種類のポリモーフィズムが可能になります。

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

そして、単純なケースでは、モナドを使用するとき、それは同じように感じます-あなたは順番に起こる表現のリストを定義します。ただし、使用するモナドによっては、コードが順番に実行され(IOモナドのように)、一度に複数のアイテムに対して並行して実行される場合(リストモナドのように)、途中で停止する場合があります(多分モナドのように)。 、途中で一時停止して後で再開する(Resumptionモナドのように)、巻き戻して最初から開始する(Transactionモナドのように)、または途中で巻き戻して他のオプションを試す場合(Logicモナドのように) 。

また、モナドは多態性であるため、必要に応じて、同じコードを異なるモナドで実行することができます。

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

于 2008-12-12T20:36:57.823 に答える
8

モナドは比喩ではありませんが、ダニエル・シュピーワクが説明するように、一般的なパターンから生まれる実用的に有用な抽象化です。

于 2010-12-27T12:48:15.143 に答える
7

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

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

于 2009-01-24T14:10:13.020 に答える
7

実際には、モナドは関数合成演算子のカスタム実装であり、副作用と互換性のない入力値と戻り値 (連鎖用) を処理します。

于 2015-11-09T13:11:00.487 に答える
7

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

于 2010-06-13T12:16:56.737 に答える
5

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

除算は2つの分数を受け入れ、分数を返しますが、これはゼロによる除算をhaskellの無限大として定義していました(これはなぜか分数です)...

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

一方、ミサイルを発射する関数がある場合は、ミサイルを発射する他の関数と一緒にそれを構成できます。これは、意図が明確であるためです。つまり、ミサイルを発射したいのですが、ミサイルを発射しようとはしません。奇妙な理由で「Hello World」を印刷します。

Haskell では、main は IO () 型または IO [()] 型であり、その区別は奇妙であり、ここでは説明しませんが、次のようなことが起こると思います。

メインがある場合、一連のアクションを実行する必要があります。プログラムを実行する理由は、通常は IO を介して効果を生成することです。したがって、メインで IO 操作を連鎖させることができます -- IO を実行するだけです。

「IOを返さない」ことをしようとすると、プログラムはチェーンが流れていないと文句を言うか、基本的に「これは私たちがやろうとしていることとどのように関連していますか-IOアクション」、それは強制的に見えるプログラマーは、道を外れてミサイルを発射することを考えずに、一連の思考を維持しながら、ソート用のアルゴリズムを作成しますが、これは流れません。

基本的に、モナドはコンパイラへのヒントのように見えます。「ねえ、ここで数値を返すこの関数を知っていますか。実際には常に機能するとは限りません。数値を生成することもあれば、何も生成しないこともあります。これをそのままにしておくだけです。マインド"。これを知っていると、モナド アクションをアサートしようとすると、モナド アクションがコンパイル時の例外として機能し、「ねえ、これは実際には数値ではありません。これは数値である可能性がありますが、これを想定することはできません。何かを実行してください。流れが受け入れられることを保証するために。」これにより、予測不可能なプログラムの動作がかなり防止されます。

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

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

「実世界」のすべてのものは、混乱を防ぐ明確な観察可能な法則に拘束されているという意味で、モナドのように見えます。これは、クラスを作成するためにこのオブジェクトのすべての操作を模倣する必要があるという意味ではありません。代わりに、単に「正方形は正方形である」、正方形に過ぎず、長方形でも円でもない、「正方形には面積がある」と言うことができます。既存の次元の 1 つの長さをそれ自体で掛けたもの. どんな正方形を持っていても、それが 2D 空間の正方形であれば、その面積は絶対にその長さの 2 乗以外の何物でもありません. 証明するのはほとんど簡単です. これは非常に強力なので.私たちの世界が現状のままであることを確認するためにアサーションを行う必要はありません。プログラムが軌道から外れるのを防ぐために現実の含意を使用するだけです。

私は間違っていることがほとんど保証されていますが、これは誰かを助けることができると思うので、誰かを助けることを願っています.

于 2013-03-16T21:26:16.477 に答える
5

そこについて学ぶときに私が最も役立った2つのことは次のとおりです。

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

チュートリアルAll About Monads。これは、それらの使用のいくつかの良い例を示しており、付録のアナロジーが私のために働いたと言わざるを得ません。

于 2009-05-16T10:38:50.813 に答える
4

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

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

于 2008-09-16T12:06:31.757 に答える
4

http://code.google.com/p/monad-tutorial/は、まさにこの問題に対処するために進行中の作業です。

于 2009-09-08T02:39:03.127 に答える
4

世界が必要としているのは別のモナドのブログ投稿ですが、これは実際に存在するモナドを特定するのに役立つと思います。

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

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

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

于 2014-10-23T14:40:26.717 に答える
3

非常に簡単な答えは次のとおりです。

モナドは、値をカプセル化するため、カプセル化された新しい値を計算するため、およびカプセル化された値をアンラップするためのインターフェースを提供する抽象化です。

それらの実際の便利な点は、ステートフルではなく状態をモデル化するデータ型を作成するための統一されたインターフェイスを提供することです。

モナドは抽象化、つまり特定の種類のデータ構造を扱うための抽象インターフェースであることを理解することが重要です。次に、そのインターフェイスを使用して、モナドの動作を持つデータ型を構築します。

Monads in Ruby, Part 1: Introduction で、非常に優れた実践的な紹介を見つけることができます。

于 2015-02-04T00:21:25.110 に答える
3

AMonadは(つまり、バイナリを持ち上げるApplicativeことができるもの、つまり" n- ary" -- (1)に機能し、純粋な値を(2)に注入できるもの) (つまり、マップできるもの、(3)つまり単項関数を(3)に持ち上げます)ネストされたデータ型を平坦化する機能を追加します ( 3 つの概念のそれぞれが対応する一連の法則に従います)。Haskell では、この平坦化操作は と呼ばれます。Functorjoin

この" "操作の一般的な (ジェネリック、パラメトリック)タイプは次のとおりです。join

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

任意のモナドに対してm(注意:m型のすべての s は同じです!)。

特定のモナドは、 type のモナド値によって「運ばれる」任意の値型に対して機能するm特定のバージョンを定義します。いくつかの特定のタイプは次のとおりです。joinam a

join  ::  [[a]]           -> [a]         -- for lists, or nondeterministic values
join  ::  Maybe (Maybe a) -> Maybe a     -- for Maybe, or optional values
join  ::  IO    (IO    a) -> IO    a     -- for I/O-produced values

このjoin操作は、タイプ値の計算をm生成するm計算を、aタイプ値の1 つの結合m計算に変換しaます。これにより、計算ステップを組み合わせて 1 つの大きな計算にすることができます。

この計算ステップ-結合 " bind " ( >>=) 演算子は、単純fmapに and をjoin一緒に使用します。つまり、

(ma >>= k)  ==  join (fmap k ma)
{-
  ma        :: m a            -- `m`-computation which produces `a`-type values
  k         ::   a -> m b     --  create new `m`-computation from an `a`-type value
  fmap k ma :: m    ( m b )   -- `m`-computation of `m`-computation of `b`-type values
  (m >>= k) :: m        b     -- `m`-computation which produces `b`-type values
-}

逆に、joinbind を介して定義できます。join mma == join (fmap id mma) == mma >>= idここid ma = maで、指定された type にとってより便利な方ですm

モナドの場合、do- 記法とそれに相当する bind -using コードの両方が、

do { x <- mx ; y <- my ; return (f x y) }        --   x :: a   ,   mx :: m a
                                                 --   y :: b   ,   my :: m b
mx >>= (\x ->                                    -- nested
            my >>= (\y ->                        --  lambda
                         return (f x y) ))       --   functions

として読むことができます

最初に "do"mxを実行し、それが完了したら、その "結果" を取得し、xそれを使用して別のことを "実行" させます。

特定のdoブロックでは、バインド矢印の右側にある各値は、ブロック全体で何らかの型と同じモナド<-の型です。m aamdo

return x-computationは与えられた純粋な値を生成する中立なm-computation であり、 -computation をバインドしてもその計算はまったく変更されません。xmreturn


(1)liftA2 :: Applicative m => (a -> b -> c) -> m a -> m b -> m c

(2)pure :: Applicative m => a -> m a

(3)fmap :: Functor m => (a -> b) -> m a -> m b

同等の Monad メソッドもあります。

liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c
return :: Monad m =>  a            -> m a
liftM  :: Monad m => (a -> b)      -> m a -> m b

モナドが与えられると、他の定義は次のように作成できます。

pure   a       = return a
fmap   f ma    = do { a <- ma ;            return (f a)   }
liftA2 f ma mb = do { a <- ma ; b <- mb  ; return (f a b) }
(ma >>= k)     = do { a <- ma ; b <- k a ; return  b      }
于 2018-08-09T13:34:37.007 に答える
2

プリンセスのF#計算式の説明は私を助けてくれましたが、私はまだ本当に理解しているとは言えません。

編集:このシリーズ-javascriptでモナドを説明する-は私にとって「バランスを崩した」ものです。

モナドを理解することはあなたに忍び寄る何かだと思います。その意味で、できるだけ多くの「チュートリアル」を読むことは良い考えですが、しばしば奇妙なもの(なじみのない言語や構文)があなたの脳が本質に集中するのを妨げます。

私が理解するのが難しかったいくつかのこと:

  • ほとんどの実際的な例は実際には単なるリターン/バインド以上のものを必要とするため、ルールベースの説明は私にはうまくいきませんでした。
  • また、それらをルールと呼んでも役に立ちませんでした。それは、「共通点があるものがあり、それらを「モナド」と呼び、共通点を「ルール」と呼びましょう」というケースです。
  • Return(a -> M<a>)とBind(M<a> -> (a -> M<b>) -> M<b>)は素晴らしいですが、私が理解できなかったのは、Bindがaからを抽出しM<a>てに渡す方法a -> M<b>です。M<a> -> aReturn( )の逆モナドに存在する必要があり、公開する必要がないことは、どこにも読んだことがないと思います(おそらく他の人には明らかです) 。
于 2009-06-03T07:56:56.063 に答える
2

モナドは、状態が変化するオブジェクトをカプセル化するために使用されるものです。ほとんどの場合、変更可能な状態を設定できない言語(Haskellなど)で発生します。

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

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

于 2008-09-04T23:33:40.583 に答える
2

Explaining monads seems to be like explaining control-flow statements. Imagine that a non-programmer asks you to explain them?

You can give them an explanation involving the theory - Boolean Logic, register values, pointers, stacks, and frames. But that would be crazy.

You could explain them in terms of the syntax. Basically all control-flow statements in C have curly brackets, and you can distinguish the condition and the conditional code by where they are relative to the brackets. That may be even crazier.

Or you could also explain loops, if statements, routines, subroutines, and possibly co-routines.

Monads can replace a fairly large number of programming techniques. There's a specific syntax in languages that support them, and some theories about them.

They are also a way for functional programmers to use imperative code without actually admitting it, but that's not their only use.

于 2011-01-14T07:08:23.370 に答える
2

モナドも理解しようとしています。それは私のバージョンです:

モナドとは、反復的なものを抽象化することです。まず、モナド自体は (抽象ジェネリック クラスのような) 型付きインターフェースであり、シグネチャを定義した bind と return の 2 つの関数があります。そして、もちろん bind と return の特定の実装を使用して、その抽象モナドに基づいて具体的なモナドを作成できます。さらに、具体的なモナドを構成/連鎖できるようにするために、bind と return はいくつかの不変条件を満たさなければなりません。

抽象化を作成するためのインターフェイス、型、クラス、およびその他のツールがあるのに、なぜモナドの概念を作成するのでしょうか? モナドはより多くのことを提供するため、ボイラープレートなしでデータを構成できるようにする方法で、問題の再考を強制します。

于 2012-05-12T21:15:09.467 に答える
2

本質的に、そして実質的に、モナドはコールバックのネストを許可
します (相互に再帰的にスレッド化された状態 (ハイフンを許してください))
(構成可能な (または分解可能な) 方法で)
(型の安全性を伴う (時には (言語によって)))
)) ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))

EG これはモナド ではありません:

//JavaScript is 'Practical'
var getAllThree = 
         bind(getFirst, function(first){  
  return bind(getSecond,function(second){  
  return bind(getThird, function(third){  
    var fancyResult = // And now make do fancy 
                      // with first, second,
                      // and third 
    return RETURN(fancyResult);
  });});});  

しかし、モナドはそのようなコードを可能にします。
モナドは、実際には次の型のセットです
{bind,RETURN,maybe others I don't know...}
これは本質的に不必要であり、実際には非現実的です。

だから今私はそれを使うことができます:

var fancyResultReferenceOutsideOfMonad =  
  getAllThree(someKindOfInputAcceptableToOurGetFunctionsButProbablyAString);  

//Ignore this please, throwing away types, yay JavaScript:
//  RETURN = K
//  bind = \getterFn,cb -> 
//    \in -> let(result,newState) = getterFn(in) in cb(result)(newState)

またはそれを分割します:

var getFirstTwo = 
           bind(getFirst, function(first){  
    return bind(getSecond,function(second){  
      var fancyResult2 = // And now make do fancy 
                         // with first and second
      return RETURN(fancyResult2);
    });})
  , getAllThree = 
           bind(getFirstTwo, function(fancyResult2){  
    return bind(getThird,    function(third){  
      var fancyResult3 = // And now make do fancy 
                         // with fancyResult2,
                         // and third 
      return RETURN(fancyResult3);
    });});

または、特定の結果を無視します。

var getFirstTwo = 
           bind(getFirst, function(first){  
    return bind(getSecond,function(second){  
      var fancyResult2 = // And now make do fancy 
                         // with first and second
      return RETURN(fancyResult2);
    });})
  , getAllThree = 
           bind(getFirstTwo, function(____dontCare____NotGonnaUse____){  
    return bind(getThird,    function(three){  
      var fancyResult3 = // And now make do fancy 
                         // with `three` only!
      return RETURN(fancyResult3);
    });});

または、次のような些細なケースを単純化します。

var getFirstTwo = 
           bind(getFirst, function(first){  
    return bind(getSecond,function(second){  
      var fancyResult2 = // And now make do fancy 
                         // with first and second
      return RETURN(fancyResult2);
    });})
  , getAllThree = 
           bind(getFirstTwo, function(_){  
    return bind(getThird,    function(three){  
      return RETURN(three);
    });});

へ ( 「Right Identity」を使用):

var getFirstTwo = 
           bind(getFirst, function(first){  
    return bind(getSecond,function(second){  
      var fancyResult2 = // And now make do fancy 
                         // with first and second
      return RETURN(fancyResult2);
    });})
  , getAllThree = 
           bind(getFirstTwo, function(_){  
    return getThird;
    });

またはそれらを一緒に詰め込みます:

var getAllThree = 
           bind(getFirst, function(first_dontCareNow){  
    return bind(getSecond,function(second_dontCareNow){  
    return getThird;
    });});

これらの機能の実用性は、 解析やモジュール/ajax/リソースの読み込みなどの
非常に厄介な問題を解決しようとするまで、実際には明らかになりません。

何千行もの indexOf/subString ロジックを想像できますか?
頻繁な解析ステップが小さな関数に含まれていたらどうなるでしょうか?
charsspacesupperCharsまたはdigits?
そして、これらの関数が、
正規表現グループや arguments.slice をいじる必要なく、コールバックで結果を返すとしたらどうでしょうか?
そして、それらの組成/分解がよく理解されているとしたら?
ボトムアップで大きなパーサーを構築できるように?

したがって、ネストされたコールバック スコープを管理する機能は、 特にモナド パーサー コンビネーター ライブラリを使用する場合に、非常に実用的です。 (つまり、私の経験上)

ハングアップしないでください:
-
圏論 - MAYBE-MONADS
-
モナド法則 - HASKELL
- !!!!

于 2015-12-08T17:41:54.887 に答える
1

Coursera の「リアクティブ プログラミングの原則」トレーニングで、Erik Meier は次のように説明しています。

"Monads are return types that guide you through the happy path." -Erik Meijer
于 2013-11-25T10:02:02.037 に答える
1

http://mikehadlow.blogspot.com/2011/02/monads-in-c-8-video-of-my-ddd9-monad.html

これはあなたが探しているビデオです。

C# で構成と型の整列に問題があることを示してから、それらを C# で適切に実装します。最後に、同じ C# コードが F# で、最後に Haskell でどのように見えるかを示します。

于 2011-05-12T20:57:48.160 に答える
0

モナドは、入れ子になった 2 つのボックスから 1 つの通常のボックスを作成できるようにする特別な機械が取り付けられたボックスですが、両方のボックスの形状の一部を保持しています。

join具体的には、タイプのを実行できますMonad m => m (m a) -> m a

returnまた、値をラップするだけのアクションも必要です。unboxes とwrapsとreturn :: Monad m => a -> m a
も言えますが、タイプではありません (すべてのモナドをアンラップするのではなく、モナドを含むモナドをアンラップします)。joinreturnjoinMonad m => m a -> a

そのため、内部にボックス ( ) を持つモナド ボックス ( Monad m =>、 ) を取り、通常のボックス ( ) を作成します。m(m a)m a

ただし、通常、Monad は(>>=)(音声の「バインド」) 演算子の観点から使用されます。これは、本質的にfmap前後joinに配置されます。具体的には、

x >>= f = join (fmap f x)
(>>=) :: Monad m => (a -> m b) -> m a -> m b

ここで、関数は ではなく 2 番目の引数にあることに注意してくださいfmap

また、join = (>>= id).

では、なぜこれが役立つのでしょうか。基本的に、ある種のフレームワーク (モナド) で作業しながら、一連のアクションを実行するプログラムを作成できます。

Haskell でのモナドの最も顕著な用途はモナドIOです。
さて、HaskellでActionIOを分類​​する型です。ここでは、モナドシステムが保存する唯一の方法でした (派手な言葉):

  • 参照透過性
  • 怠惰
  • 純度

本質的に、 のような IO アクションgetLine :: IO Stringは常に異なる型を持つため、文字列に置き換えることはできません。ものをあなたIOにテレポートさせる一種の魔法の箱と考えてください。
ただし、getLine :: IO Stringすべての関数が受け入れられると言うだけでIO a、関数が必要ない可能性があるため、騒乱が発生します。どうするconst "üp§" getLine?(constは 2 番目の引数を破棄します。const a b = a.)getLineは評価する必要はありませんが、IO を実行することになっています。aこれにより、動作がかなり予測不可能になります。また、すべての関数がと値を取るため、型システムが「純粋」ではなくなりIO aます。

IOモナドに入ります。

アクションを連結するには、ネストされたアクションをフラット化するだけです。
そしてaIO aタイプの IO アクションの出力に関数を適用するには、 を使用するだけ(>>=)です。

例として、入力された行を出力するには (行を出力するには、 の右の引数に一致する IO アクションを生成する関数です>>=):

getLine >>= putStrLn :: IO ()
-- putStrLn :: String -> IO ()

doこれは、環境を使用してより直感的に記述できます。

do line <- getLine
   putStrLn line

要するに、次のdoようなブロックです。

do x <- a
   y <- b
   z <- f x y
   w <- g z
   h x
   k <- h z
   l k w

...これに変換されます:

a     >>= \x ->
b     >>= \y ->
f x y >>= \z ->
g z   >>= \w ->
h x   >>= \_ ->
h z   >>= \k ->
l k w

(ボックス内の新しいボックスを作成するためにボックス内の値が必要ない場合)の>>演算子もあります( )と書くこともできますm >>= \_ -> fa >> b = a >>= const bconst a b = a

また、return演算子は IO 直感に基づいてモデル化されています。この場合は IO なしで、最小限のコンテキストで値を返します。inは返される型を表すため、これは命令型プログラミング言語のようなものに似ていますが、一連のアクションを停止するわけではありません! 同じです。返される用語がチェーンの前に作成されている場合にのみ役立ちます - 上記を参照してください。aIO areturn(a)f >>= return >>= gf >>= g

もちろん、他にもモナドはありますが、そうでなければモナドとは呼ばず、「IO Control」のようなものと呼ばれます。

たとえば、 List Monad ( Monad []) は、連結によって平坦化されます。これにより、(>>=)演算子はリストのすべての要素に対して機能を実行します。これは、List が多くの可能な値であり、Monad Framework がすべての可能な組み合わせを作成している「不確定性」と見なすことができます。

例 (GHCi の場合):

Prelude> [1, 2, 3] >>= replicate 3  -- Simple binding
[1, 1, 1, 2, 2, 2, 3, 3, 3]
Prelude> concat (map (replicate 3) [1, 2, 3])  -- Same operation, more explicit
[1, 1, 1, 2, 2, 2, 3, 3, 3]
Prelude> [1, 2, 3] >> "uq"
"uququq"
Prelude> return 2 :: [Int]
[2]
Prelude> join [[1, 2], [3, 4]]
[1, 2, 3, 4]

なぜなら:

join a = concat a
a >>= f = join (fmap f a)
return a = [a]  -- or "= (:[])"

Maybe Monad はNothing、それが発生した場合にすべての結果を無効にするだけです。つまり、バインディングは、関数 ( a >>= f) が返されるか、値 ( a >>= f) がNothing- であるかどうかを自動チェックしてから、同様に返しますNothing

join       Nothing  = Nothing
join (Just Nothing) = Nothing
join (Just x)       = x
a >>= f             = join (fmap f a)

または、より明示的に:

Nothing  >>= _      = Nothing
(Just x) >>= f      = f x

State Monad は、いくつかの共有状態を変更する関数用であるs -> (a, s)ため、 の引数は>>=です:: a -> s -> (a, s)
名前は一種の誤称です。State実際には状態を変更する関数のためのものであり、状態のためのものではないためです。状態自体には実際には興味深いプロパティはなく、変更されるだけです。

例えば:

pop ::       [a] -> (a , [a])
pop (h:t) = (h, t)
sPop = state pop   -- The module for State exports no State constructor,
                   -- only a state function

push :: a -> [a] -> ((), [a])
push x l  = ((), x : l)
sPush = state push

swap = do a <- sPop
          b <- sPop
          sPush a
          sPush b

get2 = do a <- sPop
          b <- sPop
          return (a, b)

getswapped = do swap
                get2

それから:

Main*> runState swap [1, 2, 3]
((), [2, 1, 3])
Main*> runState get2 [1, 2, 3]
((1, 2), [1, 2, 3]
Main*> runState (swap >> get2) [1, 2, 3]
((2, 1), [2, 1, 3])
Main*> runState getswapped [1, 2, 3]
((2, 1), [2, 1, 3])

また:

Prelude> runState (return 0) 1
(0, 1)
于 2018-08-03T22:21:52.047 に答える