70

returnモナドは通常、との順番で説明されますbind。ただし、 (および?)bindの観点からも実装できることを収集しますjoinfmap

第一級関数を欠くプログラミング言語でbindは、使用するのが非常に厄介です。join一方、非常に簡単に見えます。

ただし、どのように機能するかを完全には理解していませんjoin。明らかに、それは[Haskell]タイプを持っています

参加::モナドm=>m(mx)-> mx

リストモナドの場合、これは自明で明らかにconcatです。しかし、一般的なモナドの場合、この方法は実際に何をしますか?型アノテーションがどうなるかはわかりますが、たとえばJavaなどでこのようなものをどのように記述できるかを理解しようとしています。

(実際、それは簡単です。私はそうしません。ジェネリックが壊れているからです。;-)しかし、原則として、問題はまだ残っています...)


おっと。これは以前に尋ねられたようです:

モナド結合関数

誰かが、、およびを使用して一般的なモナドのいくつかの実装をスケッチできreturnますfmapjoin?(つまり、まったく言及>>=していません。)おそらくそれは私の愚かな脳に沈むのに役立つかもしれないと思います...

4

7 に答える 7

102

比喩の深さを配管することなく、典型的なモナドmを「aを生成する戦略」と読むことをお勧めします。したがって、このタイプm valueはファーストクラスの「値を生成する戦略」です。計算または外部相互作用のさまざまな概念にはさまざまなタイプの戦略が必要ですが、一般的な概念には、意味をなすためにいくつかの規則的な構造が必要です。

  • すでに値を持っている場合は、持っている値を生成するreturn :: v -> m v以外の何物でもない値()を生成する戦略があります。
  • ある種類の値を別の種類の値に変換する関数がある場合はfmap :: (v -> u) -> m v -> m u、戦略がその値を提供するのを待ってから変換するだけで、それを戦略()に持ち上げることができます。
  • 値を生成する戦略を生成する戦略がある場合は、値(join :: m (m v) -> m v)を生成する戦略を構築し、それが内部戦略を生成するまで外部戦略に従い、次にその内部戦略を値までたどることができます。

例を見てみましょう:葉でラベル付けされた二分木...

data Tree v = Leaf v | Node (Tree v) (Tree v)

...コインを投げて物を作る戦略を表します。戦略がLeaf vである場合、あなたがありますv; 戦略がNode h tの場合、コインを投げ、コインhが「頭」を示しているt場合、それが「尾」である場合、戦略を続行します。

instance Monad Tree where
  return = Leaf

戦略を生み出す戦略は、木にラベルが付けられた葉を持つ木です。そのような各葉の代わりに、それをラベル付けする木に移植することができます...

  join (Leaf tree) = tree
  join (Node h t)  = Node (join h) (join t)

...そしてもちろん、fmap葉のラベルを変更するだけです。

instance Functor Tree where
  fmap f (Leaf x)    = Leaf (f x)
  fmap f (Node h t)  = Node (fmap f h) (fmap f t)

これが、を生成するための戦略を生成するための戦略Intです。

木の木

コインを投げる:それが「頭」の場合、別のコインを投げて2つの戦略のどちらかを決定します(それぞれ、「0を生成するか1を生成するためのコインを投げる」または「2を生成する」)。それが「尾」である場合、3分の1を生成します(「3を生成するためにコインを投げるか、4または5のためにコインを投げる」)。

それは明らかにjoin、を生み出す戦略を立てるのに必要Intです。

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

私たちが利用しているのは、「価値を生み出す戦略」自体が価値と見なすことができるという事実です。Haskellでは、値としての戦略の埋め込みは無言ですが、英語では、戦略を使用することと単にそれについて話すことを区別するために引用符を使用します。joinオペレーターは、「何とかして戦略を作成し、それに従う」、または「戦略を言われたら、それを使用することができる」という戦略を表現します。

(メタ。この「戦略」アプローチがモナドと値/計算の区別について考えるのに適切な一般的な方法であるかどうか、またはそれが単なる別の不器用なメタファーであるかどうかはわかりません。葉のラベルが付いた木のようなタイプが役立つと思います。直感の源です。これは無料のモナドであり、モナドになるのに十分な構造を備えているため、おそらく驚くことではありませんが、それ以上はありません。)

PS「バインド」のタイプ

(>>=) :: m v -> (v -> m w) -> m w

「を生成する戦略がvあり、各vaの後続戦略がを生成するw場合は、を生成する戦略があります」と言いますw。どうすればそれを捉えることができjoinますか?

mv >>= v2mw = join (fmap v2mw mv)

各値の代わりに、それに続く生成v戦略を生成することで、生成戦略のラベルを変更できます。v2mwvwjoin

于 2012-06-28T09:46:51.680 に答える
31
join = concat -- []
join f = \x -> f x x -- (e ->)
join f = \s -> let (f', s') = f s in f' s' -- State
join (Just (Just a)) = Just a; join _ = Nothing -- Maybe
join (Identity (Identity a)) = Identity a -- Identity
join (Right (Right a)) = Right a; join (Right (Left e)) = Left e; 
                                  join (Left e) = Left e -- Either
join ((a, m), m') = (a, m' `mappend` m) -- Writer
-- N.B. there is a non-newtype-wrapped Monad instance for tuples that
-- behaves like the Writer instance, but with the tuple order swapped
join f = \k -> f (\f' -> f' k) -- Cont
于 2012-06-27T21:42:27.267 に答える
19

呼び出すと値が生成されるため、値を取得するために使用するのは非常に自然なことです。fmap (f :: a -> m b) (x ::ma)(y ::m(m b))join(z :: m b)

次に、バインドは単純にとして定義されます。これにより、さまざまな機能のKleisly構成bind ma f = join (fmap f ma)性が実現されます。これが、実際のすべてです。(:: a -> m b)

ma `bind` (f >=> g) = (ma `bind` f) `bind` g              -- bind = (>>=)
                    = (`bind` g) . (`bind` f) $ ma 
                    = join . fmap g . join . fmap f $ ma

そして、でflip bind = (=<<)私たちは

    ((g <=< f) =<<)  =  (g =<<) . (f =<<)  =  join . (g <$>) . join . (f <$>)

クライスリ構成

于 2012-06-28T17:15:25.320 に答える
17

さて、あなた自身の質問に答えるのは本当に良い形ではありませんが、それが他の誰かを啓発する場合に備えて、私の考えを書き留めておきます。(疑わしい...)

モナドが「コンテナ」と考えることができる場合、両方ともかなり明白なセマンティクスreturnを持っています。1要素のコンテナーを生成し、コンテナーのコンテナーを単一のコンテナーに変換します。それについては何も難しいことはありません。joinreturnjoin

それでは、より自然に「アクション」と考えられるモナドに焦点を当てましょう。その場合、は、 「実行」したときm xにタイプの値を生成するある種のアクションです。特別なことは何もせず、次にを生成します。を生成するアクションを実行し、それを計算して適用するアクションを作成し、結果を返します。ここまでは順調ですね。xreturn xxfmap fxxf

fそれ自体がアクションを生成する場合、最終的には。になることはかなり明白ですm (m x)。つまり、別のアクションを計算するアクションです。>>=ある意味では、アクションを実行する関数や「アクションを生成する関数」などよりも、頭を包み込む方が簡単かもしれません。

したがって、論理的に言えば、join最初のアクションを実行し、それが生成するアクションを実行してから、それを実行するようです。(むしろ、joinヘアを分割したい場合は、今説明したことを実行するアクションを返します。)

それが中心的な考え方のようです。を実装するjoinには、アクションを実行し、次に別のアクションを実行してから、それを実行します。(この特定のモナドにとって「実行」が意味するものは何でも。)

この洞察を考えると、私はいくつかのjoin実装を書くことに挑戦することができます:

join Nothing = Nothing
join (Just mx) = mx

外側のアクションがの場合はNothing、を返しNothing、そうでない場合は内側のアクションを返します。繰り返しになりMaybeますが、これはアクションというよりはコンテナのようなものなので、別のことを試してみましょう...

newtype Reader s x = Reader (s -> x)

join (Reader f) = Reader (\ s -> let Reader g = f s in g s)

それは...無痛でした。AReaderは実際には、グローバル状態を取り、その結果を返すだけの関数です。したがって、スタックを解除するには、グローバル状態を外部アクションに適用します。これにより、新しいが返されますReader。次に、この内部関数にも状態を適用します。

ある意味では、通常の方法よりもおそらく簡単です。

Reader f >>= g = Reader (\ s -> let x = f s in g x)

さて、どちらがリーダー関数で、どちらが次のリーダーを計算する関数ですか...?

Stateそれでは、古き良きモナドを試してみましょう。ここでは、すべての関数が入力として初期状態を取りますが、出力とともに新しい状態も返します。

data State s x = State (s -> (s, x))

join (State f) = State (\ s0 -> let (s1, State g) = f s0 in g s1)

それはそれほど難しくありませんでした。基本的には実行してから実行します。

今からタイピングをやめます。私の例では、すべての不具合とタイプミスを自由に指摘してください...:-/

于 2012-06-28T20:30:59.877 に答える
13

「圏論について何も知る必要はありません。実際、モナドはブリトー/宇宙服などと考えてください」というモナドの説明をたくさん見つけました。

本当に、私にとってモナドをわかりやすく説明した記事は、カテゴリーが何であるかを述べ、カテゴリーの観点からモナド(結合と​​バインドを含む)を説明し、偽のメタファーを気にしませんでした:

この記事は、数学の知識があまりなくても非常に読みやすいと思います。

于 2012-06-28T05:04:00.467 に答える
4

Haskellの型アノテーションが何をするのかを尋ねるのは、Javaのインターフェースが何をするのかを尋ねるのと似てます。

それは、ある文字通りの意味で、「しません」。(もちろん、通常、それに関連するある種の目的がありますが、それは主にあなたの頭の中にあり、ほとんどの場合、実装にはありません。)

どちらの場合も、後の定義で使用される言語でシンボルの合法的なシーケンスを宣言しています。

もちろん、Javaでは、インターフェイスはVMに文字通り実装される型アノテーションに対応していると言えるでしょう。この方法でいくつかのポリモーフィズムを取得できます。インターフェイスを受け入れる名前を定義したり、別のインターフェイスを受け入れる名前に別の定義を提供したりできます。Haskellでも同様のことが起こり、あるタイプを受け入れる名前の宣言を提供してから、別のタイプを扱うその名前の別の宣言を提供できます。

于 2012-06-28T15:11:59.213 に答える
2

これは一枚の写真で説明されているモナドです。緑のカテゴリの2つの関数は構成できません。青のカテゴリにマップすると(厳密に言えば、これらは1つのカテゴリです)、構成可能になります。モナドは、型の関数をの関数に変えることT -> Monad<U>ですMonad<T> -> Monad<U>

モナドは一枚の写真で説明しました。

于 2017-05-08T04:29:42.247 に答える