最近Haskellを手短に見てきたが、モナドが本質的に何であるかに関して、簡潔で簡潔で実用的な説明は何だろうか?
私が遭遇したほとんどの説明は、かなりアクセスしにくく、実用的な詳細に欠けていることがわかりました。
最近Haskellを手短に見てきたが、モナドが本質的に何であるかに関して、簡潔で簡潔で実用的な説明は何だろうか?
私が遭遇したほとんどの説明は、かなりアクセスしにくく、実用的な詳細に欠けていることがわかりました。
回答:
最初:数学者でない場合、モナドという用語は少し空虚です。別の用語は、それらが実際に何のために役立つかをもう少し説明する計算ビルダーです。
あなたは実用的な例を求めます:
例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))
操作char
、digit
などが非常にシンプルです。それらは一致するか、一致しません。マジックは、制御フローを管理するモナドです。操作は、一致が失敗するまで順次実行され<|>
ます。この場合、モナドは最新のものに戻り、次のオプションを試みます。繰り返しになりますが、いくつかの追加の有用なセマンティクスで操作をチェーンする方法。
例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
「モナドとは」を説明するのは、「数とは」と言うのに少し似ています。私たちはいつも数字を使います。しかし、数字について何も知らない人に出会ったと想像してみてください。どのように一体あなたは数字が何であるかを説明するだろうか?そして、それがなぜ有用であるかもしれない理由をどのように説明し始めますか?
モナドとは何ですか?簡単に言えば、これは操作を連鎖させる特定の方法です。
本質的には、実行ステップを記述し、それらを「バインド関数」でリンクしています。(Haskellでは、この名前は>>=
です。)バインド演算子への呼び出しを自分で書くか、コンパイラーに関数呼び出しを挿入させる構文シュガーを使用できます。ただし、どちらの方法でも、このバインド関数の呼び出しによって各ステップが分離されます。
したがって、バインド関数はセミコロンのようなものです。プロセスのステップを分離します。bind関数の仕事は、前のステップからの出力を受け取り、それを次のステップにフィードすることです。
難しすぎませんか?しかし、モナドには複数の種類があります。どうして?どうやって?
まあ、bind関数は 1つのステップの結果を取得し、それを次のステップに送ることができます。しかし、それが「すべて」である場合、モナドは...それは実際にはあまり役に立ちません。そして、それを理解することは重要です:すべての有用なモナドは、単にモナドであることに加えて、他の何かをします。すべての有用なモナドには「特別な力」があり、それがユニークです。
(何も特別なことをしないモナドは「恒等モナド」と呼ばれます。恒等関数のように、これはまったく無意味なことのように聞こえますが、実際にはそうではありません...しかし、それは別の話です。)
基本的に、各モナドには独自のbind関数の実装があります。また、実行ステップ間で動作するようにバインド関数を記述できます。例えば:
各ステップが成功/失敗のインジケーターを返す場合、前のステップが成功した場合にのみ、バインドで次のステップを実行できます。このようにして、失敗したステップは、ユーザーからの条件付きテストなしで、シーケンス全体を「自動的に」中止します。(失敗モナド。)
このアイデアを拡張して、「例外」を実装できます。(エラーモナドまたは例外モナド。)言語機能ではなく、自分で定義するため、それらがどのように機能するかを定義できます。(たとえば、最初の2つの例外を無視し、3番目の例外がスローされたときにのみ中止したい場合があります。)
各ステップで複数の結果を返すようにして、バインド関数をループさせ、それぞれを次のステップに渡すことができます。このようにして、複数の結果を処理するときにループをどこにでも書き続ける必要はありません。bind関数は、「自動的に」すべてを行います。(リストモナド。)
あるステップから別のステップに「結果」を渡すだけでなく、bind関数に追加のデータを渡すこともできます。このデータはソースコードに表示されなくなりましたが、手動ですべての関数に渡す必要がないため、どこからでもアクセスできます。(読者モナド。)
「余分なデータ」を差し替えられるようにすることができます。これにより、実際に破壊的な更新を行わなくても、破壊的な更新をシミュレートできます。(状態モナドとそのいとこ作家モナド。)
破壊的な更新をシミュレートしているだけなので、実際の破壊的な更新では不可能なことを簡単に行うことができます。たとえば、最後の更新を元に戻したり、以前のバージョンに戻すことができます。
計算を一時停止できるモナドを作成できるので、プログラムを一時停止し、内部状態データをいじってから再開できます。
「継続」をモナドとして実装できます。これにより、人々の心を壊すことができます!
これ以上のすべてはモナドで可能です。もちろん、これらすべてはモナドがなくても完全に可能です。モナドを使用するほうがはるかに簡単です。
実際、モナドの一般的な理解に反して、彼らは国家とは何の関係もありません。モナドは単に物をラップする方法であり、ラップされたものをアンラップせずに操作を行うメソッドを提供します。
たとえば、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
)を指定し、モナドreturn
とbind
演算の動作を指定します。
クールなのは、これがあちこちに出現する一般的なパターンであることが判明し、純粋な方法で状態をカプセル化することはそのうちの1つにすぎないことです。
HaskellのIOモナドで使用されているように、モナドを使用して機能の依存関係を導入し、評価の順序を制御する方法に関する優れた記事については、IO Insideをチェックしてください。
モナドの理解については、あまり気にしないでください。興味のあることを読んでください。すぐに理解できなくても心配はいりません。次に、Haskellのような言語でダイビングするだけです。モナドはこれらの事柄の1つであり、練習によって理解が脳に入り込み、ある日突然あなたはそれらを理解していることに突然気づきます。
sigfpeさんのコメント:
しかし、これらはすべて、説明が必要な難解なものとしてモナドを導入しています。しかし、私が主張したいのは、それらはまったく難解ではないということです。実際、関数型プログラミングでさまざまな問題に直面すると、容赦なく、特定のソリューションに導かれることになりますが、そのすべてがモナドの例です。実際、まだお持ちでない場合は、すぐに発明していただければ幸いです。これらのソリューションはすべて、実際には変装された同じソリューションであることに気づくための小さなステップです。そして、これを読んだ後は、モナドに関する他のドキュメントを理解する方が良いかもしれません。
モナドが解決しようとする問題の多くは、副作用の問題に関連しています。それでは、それらから始めましょう。(モナドを使用すると、副作用の処理以上のことができることに注意してください。特に、多くの種類のコンテナオブジェクトをモナドと見なすことができます。もう1つ。)
C ++などの命令型プログラミング言語では、関数は数学の関数のように動作しません。たとえば、単一の浮動小数点引数を取り、浮動小数点の結果を返すC ++関数があるとします。表面的には、実数を実数にマッピングする数学関数のように見えるかもしれませんが、C ++関数は、引数に依存する数値を返すだけではありません。グローバル変数の値を読み書きしたり、画面に出力を書き込んだり、ユーザーからの入力を受け取ったりできます。ただし、純粋な関数型言語では、関数は引数で指定されたもののみを読み取ることができ、世界に影響を与えることができる唯一の方法は、返される値を使用することです。
モナドは>>=
(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コードを書く必要があります。
まず、ファンクタとは何かを理解する必要があります。その前に、高次関数を理解してください。
高次関数は、単に引数として関数を取る関数です。
ファンクタは、任意のタイプの構造であるT
高次機能が存在するため、それを呼び出してmap
、その変換タイプの機能はa -> b
(任意の二種類を与えa
及びb
機能に)T a -> T b
。このmap
関数は、次の式がすべてp
とq
(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)
各要素の関数を呼び出して、結果のリストを返します。
モナドは、本質的に単にファンクタであるT
2つの追加方法と、join
型のT (T a) -> T a
、およびunit
(と呼ばれることもあるreturn
、fork
または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)
。そしてpure
、join
そのようなもののためのアイデンティティでなければなりませんjoin (pure x) == x
。
[免責事項:私はまだモナドを完全に理解しようとしています。以下は、私がこれまでに理解したことです。それが間違っているなら、うまくいけば、知識のある誰かがカーペットの上で私を呼ぶでしょう。]
アーナーは書いた:
モナドは単に物をラップする方法であり、ラップされたものをアンラップせずに操作を行うメソッドを提供します。
それだけです。アイデアは次のようになります。
ある種の価値を取り、それをいくつかの追加情報でラップします。値が特定の種類(整数や文字列など)であるように、追加情報も特定の種類です。
たとえば、その追加情報はまたはである可能性がMaybe
ありIO
ます。
次に、ラップされたデータを操作しながら、その追加情報を処理できるいくつかの演算子があります。これらの演算子は、追加情報を使用して、ラップされた値に対する操作の動作を変更する方法を決定します。
たとえば、a Maybe Int
はJust Int
またはNothing
です。ここで、a Maybe Int
をに追加するとMaybe Int
、演算子は両方Just Int
が内にあるかどうかを確認し、そうである場合は、Int
sのラップを解除し、追加演算子を渡して、結果Int
を新しいJust Int
(これは有効です)に再ラップします。Maybe Int
)、したがってを返しますMaybe Int
。しかし、それらの1つがNothing
内部にある場合、この演算子はただちに戻りNothing
ますMaybe Int
。これも有効です。そうすることで、Maybe Int
sが通常の数であるかのように見せかけ、通常の計算を実行できます。を取得する場合Nothing
でも、方程式は正しい結果を生成しますNothing
– あらゆる場所でチェックをくずす必要はありません。
しかし、その例はまさにのために何が起こるかですMaybe
。追加情報がである場合、sにIO
定義されたその特別な演算子IO
が代わりに呼び出され、追加を実行する前にまったく異なる何かを実行できます。(OK、2つIO Int
のsを一緒に追加することはおそらく無意味です–まだわかりません。)(また、Maybe
例に注意を払った場合、「値を余分なものでラップする」ことが常に正しいとは限らないことに気付きました。しかし、それは難しいです。不可解なことなく正確、正確、正確であること。)
基本的に、「モナド」はおおまかに「パターン」を意味します。しかし、非公式に説明され、具体的に名前が付けられたパターンが満載の本の代わりに、新しい構文をプログラムの中で物事として宣言できるようにする言語構造(構文など)があります。(ここでの不正確さは、すべてのパターンが特定の形式に従う必要があるため、モナドはパターンほど一般的ではありません。しかし、ほとんどの人が知って理解している最も近い用語だと思います。)
そして、それが人々がモナドをとても混乱させる理由です:それらはとても一般的な概念だからです。何かをモナドにするものを尋ねるのは、何かをパターンにするものを尋ねるのと同様にあいまいです。
しかし、パターンのアイデアを言語で構文的にサポートすることの意味を考えてください。Gangof Fourの本を読んで特定のパターンの構成を覚える代わりに、不可知論者にこのパターンを実装するコードを書くだけです。一度一般的な方法で、それで完了です!その後、ビジター、ストラテジー、ファサードなど、このパターンを再利用できます。コードで操作を装飾するだけで、何度も再実装する必要はありません。
だからこそ、モナドを理解している人々がモナドをとても便利だと思うのです。知的スノッブが理解に誇りを持っているのは象牙の塔の概念ではありませんが(もちろん、もちろんteehee)、実際にはコードが単純になります。
M (M a) -> M a
。それをタイプの1つに変えることができるという事実M a -> (a -> M b) -> M b
は、それらを有用にするものです。
多くの努力の結果、モナドがようやく理解できたと思います。圧倒的にトップ投票された回答についての私自身の長い批評をもう一度読んだ後、私はこの説明を提供します。
モナドを理解するために答える必要がある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
通常の関数を特定の種類のバインド演算子で機能する関数に変換する演算子に渡します。
モナドはどのように実装されていますか?
他の回答を参照してください。その詳細に飛び込むのは自由です。
数年前にこの質問に答えた後、私はその応答を改善して簡素化できると信じています...
モナドは、合成関数を使用して一部の入力シナリオの処理を外部化し、合成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
。もちろん、この関数bind
はM
、構造を検査して必要な種類のアプリケーションを実行できるように、特定のユーザー向けに具体的に定義されます。それでも、アプリケーションが必要であると判断したときに、検査されていない関数を合成関数に渡すだけなa
ので、は何でもかまいbind
ませa
ん。さらに、合成された関数自体が、M
入力構造の一部も単純化します。したがって...
(a -> Mb) >>= (b -> Mc)
より簡潔に Mb >>= (b -> Mc)
つまり、モナドは外部化し、入力が十分に公開されるように設計された後、特定の入力シナリオの処理に関する標準的な動作を提供します。この設計はshell and content
、シェルが構成された関数のアプリケーションに関連するデータを含み、質問され、そのbind
関数でのみ使用可能なモデルです。
したがって、モナドは次の3つです。
M
モナド関連情報を保持するためのシェル、 bind
シェル内で見つかったコンテンツ値への合成関数の適用でこのシェル情報を利用するために実装された関数、および a -> Mb
モナディック管理データを含む結果を生成するフォームの構成可能な関数。一般的に言えば、関数への入力は、エラー条件などを含む可能性がある出力よりもはるかに制限されています。したがって、Mb
結果の構造は一般的に非常に役立ちます。たとえば、除数がの場合、除算演算子は数値を返しません0
。
また、monad
Sは、ラップ値ことラップ機能を含んでいてもよい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
適用されたかに関係なく、評価の順序が保持されることを意味します。すなわち、の定義の中Associativity
括弧の力、上記の早期評価binding
のf
とg
だけ期待する機能になりますMa
ためには完了することbind
。したがって、の評価はMa
、その値が適用される前に決定する必要がf
あり、その結果はに適用されg
ます。
モナドは、事実上、「型演算子」の一種です。3つのことを行います。最初に、あるタイプの値を別のタイプ(通常は「モナディックタイプ」と呼ばれます)に「ラップ」(または変換)します。第2に、モナディック型で使用できる基本型で使用できるすべての操作(または関数)を作成します。最後に、自身を別のモナドと組み合わせて複合モナドを生成するためのサポートを提供します。
「多分モナド」は、Visual Basic / C#の「null許容型」と本質的に同等です。nullを許容しない型 "T"を取り、それを "Nullable <T>"に変換してから、すべての2項演算子がNullable <T>で何を意味するかを定義します。
副作用は同じように表されます。関数の戻り値とともに副作用の説明を保持する構造が作成されます。次に、「リフトされた」操作は、関数間で値が渡されるときに副作用を回避します。
これらは、いくつかの理由により、「タイプ演算子」のわかりやすい名前ではなく、「モナド」と呼ばれています。
モナドへの良い動機はsigfpe(Dan Piponi)のあなたがモナドを発明したかもしれません!(そして多分あなたはすでに持っています)。他にもたくさんのモナドチュートリアルがありますが、その多くは誤ってモナドをさまざまな類推を使って「簡単な用語」で説明しようとしています。これはモナドチュートリアルの誤りです。それらを避けてください。
マカイバー博士が言っているように、あなたの言語が悪い理由を教えてください:
だから、Haskellについて嫌いなこと:
明白から始めましょう。モナドのチュートリアル。いいえ、モナドではありません。特にチュートリアル。彼らは無限で、誇張され、親愛なる神です。さらに、それらが実際に役立つという説得力のある証拠を見たことがありません。クラス定義を読み、コードを書き、恐ろしい名前を乗り越えてください。
Maybeモナドを理解していると?いいでしょう、あなたは途中です。他のモナドの使用を開始するだけで、遅かれ早かれ、一般的なモナドが何であるかを理解できます。
[数学を志向している場合は、数十のチュートリアルを無視して定義を学ぶか、カテゴリ理論の講義に従うことをお勧めします。)定義の主な部分は、モナドMがそれぞれについて定義する「型コンストラクタ」を含むことです既存のタイプ「T」、新しいタイプ「MT」、および「通常の」タイプと「M」タイプの間を行き来するためのいくつかの方法。]
また、驚くべきことに、モナドへの最良の紹介の1つは、実際にはモナドを紹介する初期の学術論文の1つである関数型プログラミングのためのフィリップワドラーのモナドです。実際の人工チュートリアルの多くとは異なり、実際には実用的で重要な動機付けの例があります。
モナドは、データに対する抽象データ型とは何かをフローを制御するためのものです。
つまり、多くの開発者は、セット、リスト、辞書(またはハッシュ、マップ)、およびツリーの概念に慣れています。これらのデータ型には、多くの特殊なケースがあります(たとえば、InsertionOrderPreservingIdentityHashMap)。
ただし、プログラムの「フロー」に直面すると、多くの開発者は、if、switch / case、do、while、goto(grr)、および(たぶん)クロージャーよりもはるかに多くの構造にさらされていません。
したがって、モナドは単に制御フロー構造です。モナドを置き換えるより良いフレーズは「コントロールタイプ」でしょう。
そのため、モナドには制御ロジック、ステートメント、または関数用のスロットがあります。データ構造で同等のものとは、一部のデータ構造ではデータを追加および削除できるということです。
たとえば、「if」モナド:
if( clause ) then block
最も単純なのは、2つのスロット(節とブロック)です。if
モナドは通常、節の結果を評価するために構築され、偽でない場合は、ブロックを評価しています。多くの開発者は、「もし」を学ぶときにモナドを紹介されていません。効果的なロジックを書くためにモナドを理解する必要はありません。
モナドは、データ構造がより複雑になるのと同じように、より複雑になる可能性がありますが、モナドには、セマンティクスが似ているが実装や構文が異なる可能性のある幅広いカテゴリが多数あります。
もちろん、データ構造を反復したり、トラバースしたりするのと同じ方法で、モナドを評価できます。
コンパイラは、ユーザー定義のモナドをサポートしている場合とサポートしていない場合があります。Haskellは確かにそうです。Iokeにはいくつかの同様の機能がありますが、モナドという用語はこの言語では使用されていません。
私のお気に入りのモナドチュートリアル:
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」)があります。
最近、モナドを別の方法で考えています。私は、それらを実行順序を数学的に抽象化し、新しい種類の多態性を可能にするものと考えてきました。
命令型言語を使用していて、いくつかの式を順番に記述した場合、コードは常にその順序で正確に実行されます。
そして、単純なケースでは、モナドを使用するときも同じように感じられます-順番に発生する式のリストを定義します。ただし、使用するモナドによっては、コードが順番に(IOモナドのように)順番に実行され、(Listモナドのように)一度に複数のアイテムで並列に実行され、途中で停止する場合があります(Maybeモナドのように) 、途中で一時停止して後で再開する(再開モナドのように)、巻き戻して最初から開始する(トランザクションモナドのように)、または途中で巻き戻して他のオプションを試す(ロジックモナドのように) 。
また、モナドはポリモーフィックであるため、ニーズに応じて、異なるモナドで同じコードを実行することが可能です。
さらに、場合によっては、モナドを(モナド変換子と)組み合わせて、同時に複数の機能を取得することができます。
私はまだモナドは初めてですが、読んで本当に良かったと感じたリンクを共有したいと思いました(写真付き!!):http : //www.matusiak.eu/numerodix/blog/2012/3/11/素人向けモナド/ (所属なし)
基本的に、私が記事から得た温かくファジーなコンセプトは、モナドは基本的に、さまざまな関数を構成可能な方法で機能させるアダプターです。つまり、複数の関数をつなぎ合わせて、一貫性のない戻りを心配することなくそれらを組み合わせることができます。タイプなど。したがって、BIND関数は、これらのアダプターを作成するときに、リンゴとリンゴを、オレンジとオレンジを保つ役割を果たします。また、LIFT関数は、「下位レベル」の関数を取り、それらを「アップグレード」してBIND関数と連携し、同様に構成できるようにします。
私はそれが正しく理解されたことを願っています、そしてさらに重要なことに、記事がモナドについて有効な見解を持っていることを願っています。他に何もなければ、この記事はモナドについてもっと学ぶための私の食欲を刺激するのに役立ちました。
上記の優れた回答に加えて、モナドをJavaScriptライブラリjQuery(および「メソッドチェーン」を使用してDOMを操作する方法)に関連付けることによってモナドを説明する(Patrick Thomsonによる)次の記事へのリンクを提供します。: jQueryはモナドです
jQueryのドキュメント自体は、用語「モナド」を参照してください。おそらく、より馴染みのある「ビルダーパターン」について協議しません。これは、気づかないうちに適切なモナドがあるという事実を変更するものではありません。
モナドは、共通のコンテキストを共有する計算を組み合わせる方法です。パイプのネットワークを構築するようなものです。ネットワークを構築するとき、そこを流れるデータはありません。しかし、すべてのビットを「バインド」と「リターン」でつなぎ合わせた後、次のようなものを呼び出しrunMyMonad monad data
、データがパイプを流れます。
実際には、モナドは、副作用と互換性のない入力値と戻り値(連鎖用)を処理する関数構成演算子のカスタム実装です。
私が正しく理解していれば、IEnumerableはモナドから派生しています。それは、C#の世界の私たちにとって興味深いアプローチの角度かもしれませんか?
価値のあるものとして、私を助けてくれたチュートリアルへのリンクをいくつか紹介します(いいえ、モナドとはまだ理解していません)。
そこについて学ぶときに私を最もよく助けた2つのことは:
Graham Huttonの著書 『Programming in Haskell』の第8章「Functional Parsers」。これは実際にはモナドについてはまったく触れていませんが、章を読み進めてその中のすべて、特にバインド操作のシーケンスがどのように評価されるかを本当に理解できれば、モナドの内部について理解できます。これには数回の試行が必要です。
チュートリアルAll About Monads。これはそれらの使用のいくつかの良い例を与えており、私が私のために働いたアペンデックスの類推を言わなければなりません。
モノイドは、モノイドとサポートされている型で定義されたすべての操作が常にモノイド内でサポートされている型を返すことを保証するもののようです。たとえば、任意の数+任意の数=数、エラーなし。
一方、除算は2つの小数を受け入れ、小数を返します。これは、ゼロによる除算をhaskell somewhy(たまたま小数のsomewhy)の無限大として定義しました...
いずれにせよ、モナドは操作のチェーンが予測可能な方法で動作することを保証するための単なる方法であり、Num-> Numであると主張する関数は、xで呼び出されたNum-> Numの別の関数で構成されていません言う、ミサイルを発射します。
一方、ミサイルを発射する関数がある場合は、ミサイルを発射したいが、ミサイルを発射する他の関数と組み合わせて、ミサイルを発射することができます。なんらかの理由で「Hello World」を印刷しています。
Haskellでは、mainのタイプはIO()またはIO [()]であり、違いは奇妙であり、これについては説明しませんが、ここで私が考えていることは次のとおりです。
メインがある場合は、一連のアクションを実行したいのですが、プログラムを実行する理由は、通常はIOですが、効果を生み出すためです。したがって、私はIO操作をメインで一緒にチェーンして-IOを行うだけで、他には何もできません。
「IOを返さない」ことをやろうとすると、プログラムはチェーンが流れない、または基本的に「これが私たちがやろうとしていること、つまりIOアクションにどのように関係するのか」と文句を言うでしょう。プログラマーは、並べ替えのアルゴリズムを作成する一方で、ミサイルの発射について迷ったり、考えたりすることなく、思考の流れを維持します。
基本的に、モナドはコンパイラへのヒントのように見えます。「ねえ、ここで数値を返すこの関数は知っています。実際に機能するわけではなく、数値を生成することもあれば、まったく何もしないこともあります。マインド"。これを知っていて、モナディックアクションをアサートしようとすると、モナディックアクションはコンパイル時の例外として機能し、「これは実際には数値ではありません。これは数値である可能性がありますが、これを想定することはできません。流れが許容範囲内であることを確認してください。」これにより、予測できないプログラムの動作が防止されます。
モナドは純粋さや制御ではなく、すべての動作が予測可能で定義されている、またはコンパイルされないカテゴリのIDを維持することに関するものであるように見えます。何かをすることが期待されているときは何もできず、何もしない(目に見える)ことが期待されている場合は何もできない。
モナドについて私が考えることができる最大の理由は-手続き型/ OOPコードを見ると、プログラムの開始点も終了点もわからないことがわかります。 、魔法、そしてミサイル。このコンテキストのモジュール性は相互に依存する「セクション」に基づいているため、それを維持できなくなり、可能であれば、プログラムの一部を理解する前にプログラム全体にかなりの時間を費やすことになります。コードの効率性と相互関係を約束するために、コードができるだけ関連するように最適化されています。モナドは非常に具体的であり、定義によって明確に定義されており、プログラムのフローが分析可能であり、分析が困難な部分を分離できるようにします。モナドは「または、宇宙を破壊したり、時間を歪めたりすることもできます。モナドはそれが何であるかを保証します。これは非常に強力です。または、宇宙を破壊したり、時間を歪めたりすることもできます。モナドはそれが何であるかを保証します。これは非常に強力です。
「現実世界」のすべてのものは、混乱を防ぐ明確な観察可能な法則に拘束されるという意味で、モナドであるように見えます。これは、クラスを作成するためにこのオブジェクトのすべての操作を模倣する必要があることを意味するのではなく、単に「正方形は正方形」、正方形のみ、長方形でも円でも、「正方形には面積既存の次元の長さのそれ自体を掛けたものです。どんな正方形を持っているとしても、2Dスペースの正方形の場合、その領域は絶対に長さの2乗以外には絶対にありえません。これは証明するのがほとんど自明です。私たちは、私たちの世界が現在の状態であることを確認するためにアサーションを作成する必要はありません。プログラムが軌道から外れるのを防ぐために、現実の影響を使用します。
間違いは間違いなく保証されていますが、これはそこにいる誰かを助けることができると思うので、うまくいけば誰かを助けることができます。
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厳密に定義圏の関数型プログラミングにおけるモナドは、の定義と同じではありません圏論におけるモナドのターンで定義されている、map
とflatten
。それらは特定のマッピングの下では一種の同等ですが。このプレゼンテーションは非常に優れています。http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category
この答えは、やる気を起こさせる例から始まり、例を通して働き、モナドの例を導き出し、正式に「モナド」を定義します。
疑似コードで次の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
している渡す x
にf
、取得<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 int
int型と文字列のペアです。アンは、M string
文字列と文字列のペアです。等。
おめでとうございます、モナドを作成しました!
正式には、モナドはタプル<M, feed, wrap>
です。
モナドは次のタプル<M, feed, wrap>
です:
M
型コンストラクタです。feed
(とる関数取りt
及び戻りM u
)とM t
戻りますM u
。wrap
を取り、v
を返しますM v
。t
、u
、およびv
、または同じであってもなくてもよい任意の3つのタイプがあります。モナドは、特定のモナドについて証明した3つのプロパティを満たします。
摂食包まれたt
関数にすることは同じである渡す開封さをt
関数に。
正式に: feed(f, wrap(x)) = f(x)
にをフィードM t
しwrap
ても、には何も起こりませんM t
。
正式に: feed(wrap, m) = m
摂食M t
(それを呼び出すm
ことを関数に)
t
へのg
M u
(それを呼び出すn
)を取得しますg
n
するf
と同じです
m
へg
n
から得るg
n
へf
正式:feed(h, m) = feed(f, feed(g, m))
どこh(x) := feed(f, g(x))
通常、feed
はbind
(>>=
HaskellのAKA )とwrap
呼ばれ、と呼ばれreturn
ます。
Monad
Haskellのコンテキストで説明しようと思います。
関数型プログラミングでは、関数の構成が重要です。これにより、プログラムを小さくて読みやすい関数で構成できます。
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
非常に似ています。g
f
(f1 . g1) x
or f1 (g1 x)
はできません。x
はInt
値です。が返す結果のタイプは、予期したg1
ものでf1
はありません。
私たちは、構成することができf
とg
して.
オペレータが、今私たちは、作曲することができないf1
とg1
して.
。問題は、コンテキストにない値を期待する関数に、コンテキスト内の値を直接渡すことができないことです。
作成できるようにg1
and f1
に演算子を導入すると便利だと(f1 OPERATOR g1) x
思いませんか?g1
コンテキストの値を返します。値はコンテキストから取り出され、に適用されf1
ます。はい、そのような演算子があります。です<=<
。
>>=
構文は少し異なりますが、まったく同じことを行う演算子もあります。
書きます:g1 x >>= f1
。g1 x
あるMaybe Int
値。>>=
オペレータは、その取るのに役立ちますInt
「おそらく、ない-そこに」コンテキストのうちの値を、そしてそれを適用しますf1
。の結果でf1
あるは、操作Maybe Bool
全体の結果になります>>=
。
そして最後に、なぜMonad
有用なのですか?Monad
は>>=
演算子を定義する型クラスなのでEq
、==
and /=
演算子を定義する型クラスとほとんど同じです。
結論として、Monad
型クラスは、>>=
コンテキスト内の値を期待しない関数にコンテキスト内の値(これらのモナド値と呼ぶ)を渡すことができる演算子を定義します。コンテキストが処理されます。
ここで覚えておくべきことが1つあります。それは、Monad
がコンテキストの値を含む関数の合成を許可するということです。
{-# 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 x
(infixl 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 x
a -> [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
状態へのアクセスは、プリミティブget
とput
、ステートフルモナドを抽象化するメソッドによって提供されます。
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}
class Monad m => Stateful m st | m -> st where
get :: m st
put :: st -> m ()
m -> st
モナドの状態タイプの機能依存を宣言します。すなわち、例えば、状態の種類があることを決定する一意。st
m
State t
t
instance Stateful (State st) st where
get :: State st st
get = State $ \ s -> (s, s)
put :: st -> State st ()
put s = State $ \ _ -> ((), s)
void
C と同様に使用される単位タイプ
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)
ここでX
、Y
はのオブジェクトですC
。HomC(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
)、恒等射eta
(return
)、および拡張演算子*
(=<<
)。
の各クライスリ射 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
Philip Wadler:関数型プログラミングのモナド
Simon L Peyton Jones、Philip Wadler:命令型関数プログラミング
ジョナサンMDヒル、キースクラーク:カテゴリ理論の紹介、カテゴリ理論モナド、および関数型プログラミングとの関係 ´
Eugenio Moggi:計算とモナドの概念
しかし、なぜそんなに抽象的な理論がプログラミングに役立つのでしょうか?
答えは簡単です。コンピュータ科学者として、私たちは抽象化を大切にしています!我々はソフトウェアコンポーネントへのインタフェースを設計するとき、我々はしたいが、実装についてできるだけ明らかにすること。実装を多くの代替手段、同じ「概念」の多くの他の「インスタンス」に置き換えることができるようにしたいと考えています。多くのプログラムライブラリへの汎用インターフェイスを設計する場合、選択するインターフェイスにさまざまな実装があることがさらに重要です。私たちが非常に高く評価しているのはモナド概念の一般性です。それは、カテゴリー理論が非常に抽象的であり、その概念がプログラミングに非常に役立つためです。
したがって、以下に示すモナドの一般化も、カテゴリー理論と密接な関係があることは驚くに値しません。しかし、私たちの目的は非常に実用的であることを強調します。「カテゴリ理論を実装する」ことではなく、コンビネータライブラリを構築するより一般的な方法を見つけることです。数学者が私たちのためにすでに多くの仕事をしてきたのは、単に私たちの幸運です!
矢印に一般化モナドジョン・ヒューズ
世界に必要なのは別のモナドブログ投稿ですが、これは野生の既存のモナドを識別するのに役立つと思います。
上記はシェルピンスキーの三角形と呼ばれるフラクタルで、私が覚えている唯一のフラクタルです。フラクタルは、上の三角形のような自己相似構造であり、部分は全体に似ています(この場合、親の三角形の縮尺のちょうど半分)。
モナドはフラクタルです。モナディックデータ構造が与えられた場合、その値を組み合わせてデータ構造の別の値を形成できます。これがプログラミングに役立つ理由であり、多くの状況で発生する理由です。
http://code.google.com/p/monad-tutorial/は、この質問に正確に対処するために進行中の作業です。
以下の「{| 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
だけを求めに来たのa
。Reader r
モナドはに似ているState st
、それだけでできますが、f
見てr
。
これらすべての要点は、それ自体がモナドであると宣言されているすべてのタイプのデータが、モナドからの値の抽出に関する何らかのコンテキストを宣言しているということです。このすべてから大きな利益は?まあ、なんらかのコンテキストで計算を行うのは簡単です。ただし、複数のコンテキストが含まれる計算をつなぎ合わせると、面倒になる可能性があります。モナド操作は、プログラマーが必要としないように、コンテキストの相互作用を解決します。
を使用すると、>>=
から自律性の一部を取り除くことにより、混乱を緩和できますf
。つまりNothing
、たとえば上記の場合、の場合はf
何をすべきかを決定することができなくなりNothing
ます。でエンコードされてい>>=
ます。これはトレードオフです。以下のためにそれが必要だった場合f
の場合には何をすべきかを決定するためにNothing
、その後f
から機能していなければならないMaybe a
とMaybe b
。この場合、Maybe
モナドであることは無関係です。
ただし、データ型がそのコンストラクター(IOを確認)をエクスポートしない場合があることに注意してください。アドバタイズされた値を操作する場合は、モナディックインターフェイスを使用するしかありません。
モナドは、状態が変化するオブジェクトをカプセル化するために使用されるものです。他の方法では変更可能な状態を許可しない言語(Haskellなど)で最も頻繁に発生します。
例として、ファイルI / Oがあります。
ファイルI / Oにモナドを使用して、状態の変化の性質を、モナドを使用したコードだけに分離できます。モナド内のコードはモナド外の世界の変化する状態を効果的に無視することができます-これはあなたのプログラムの全体的な効果について推論することをずっと簡単にします。