web-dev-qa-db-ja.com

Bind()の代わりにJoin()を使用したモナド

モナドは通常、returnbindの順番で説明されます。ただし、bind(およびjoin?)に関してfmapを実装することもできます。

ファーストクラスの関数が不足しているプログラミング言語では、bindを使用するのは非常に厄介です。一方、joinは非常に簡単に見えます。

ただし、joinの仕組みを完全に理解しているとは思いません。明らかに、それは[Haskell]タイプを持っています

結合::モナドm => m(m x)-> m x 

リストモナドの場合、これは簡単で明らかにconcatです。しかし、一般的なモナドでは、操作上、このメソッドは実際に何をするのでしょうか?型シグネチャの機能はわかりますが、たとえばJavaまたは同様のもの)でこのように書く方法を理解しようとしています。

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


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

モナド結合関数

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

64

比喩の深みを掘り下げることなく、典型的なモナドmを「生成する戦略」と読むことをお勧めします。したがって、型_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を生成する戦略を生成する戦略です。

tree of trees

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

これは明らかにjoinsがIntを生成する戦略を立てるまでです。

enter image description here

私たちが利用しているのは、「価値を生み出す戦略」それ自体が価値と見なすことができるという事実です。 Haskellでは、戦略を値として埋め込むことは静かですが、英語では、引用符を使用して、戦略の使用とそれを話すことだけを区別しています。 join演算子は、「何らかの方法で生産してから戦略に従う」という戦略、または「もしあなたがtold戦略なら、---se itでよい」と表現します。

(メタ。この「戦略的」アプローチがモナドと値/計算の区別について考えるのに適切に一般的な方法であるかどうか、またはそれが単なる別の卑劣なメタファーであるかどうかはわかりません。 freeモナドであるため、おそらく直感のソースです。これは、モナドになるのに十分なだけの構造ですが、それ以上はありません。)

PS「バインド」のタイプ

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

vを生成する戦略があり、vごとにwを生成する後続の戦略がある場合、wを生成する戦略があります」と言います。 joinの観点からそれをどのように捉えることができますか?

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

_v2mw_によってv- producing戦略のラベルを変更し、各v値の代わりに、それに続くw- producing戦略を生成することができます— joinの準備ができました!

93
pigworker
join = concat -- []
join f = \x -> f x x -- (e ->)
join f = \s -> let (s', f') = 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 (m, (m', a)) = (m `mappend` m', a) -- Writer
join f = \k -> f (\f' -> f' k) -- Cont
26
Daniel Wagner

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

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

それでは、より自然に「アクション」と考えられるモナドに焦点を当てましょう。その場合、_m x_は、「実行」するとx型の値を生成する何らかのアクションです。 _return x_は特別な処理を行わず、xを生成します。 _fmap f_は、xを生成するアクションを実行し、xを計算してfを適用するアクションを作成し、結果を返します。ここまでは順調ですね。

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

それは...痛みがありませんでした。 Readerは、実際にはグローバルな状態を取り、その結果のみを返す関数です。したがって、スタックを解除するには、グローバル状態を外部アクションに適用します。これにより、新しい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)
_

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

入力をやめます。私の例のすべての不具合とタイプミスを指摘してください...:-/

14

モナドについて「カテゴリ理論について何も知る必要はありません。実際には、モナドをブリトー/宇宙服/なんでも考えてみてください」という説明がたくさんあります。

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

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

12
solrize

fmap (f :: a -> m b) (x ::を呼び出していますm_a)_は値を生成します_(y ::_m_(m b))_したがって、joinを使用して値_(z :: m b)_を取得するのは非常に自然なことです。

次に、bindは単にbind ma f = join (fmap f ma)として定義されているため、_(:: a -> m b)_多様な関数の Kleisly構成性 を実現しています。

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

したがって、flip bind = (=<<)を使用すると、 we have

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

Kleisli composition

11
Will Ness

Haskell doesの型シグネチャを尋ねるのは、Java does

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

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

もちろん、Javaでは、インターフェイスはVMに文字通り実装される型シグネチャに対応すると言えます。このようにしてポリモーフィズムを取得できます。インターフェースを受け入れる名前を定義し、別のインターフェースを受け入れる名前に別の定義を提供できます。 Haskellでも同様のことが起こり、1つのタイプを受け入れる名前の宣言を提供し、次に、別のタイプを処理するその名前の別の宣言を提供できます。

3
rdm

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

Monad explained in one picture.

1
Dagang