11

私はモナドについて学んでいて、いくつか質問があります。

これが私が今いる場所です。私が間違っているところを修正してください。

  • >>=記号は中置演算子です。中置演算子は、2 つの引数 (左側と右側) を取り、値を返す関数です。

  • >>=シンボルはバインド演算子と呼ばれ、署名がありますMonad m => m t -> (t -> m u) -> m u。ただし、タイプはここに並んでいないようです。type の値を取得しm t、2 番目の引数はt. (ドットを接続する方法がわかりません。)

  • これは、 bind 関数がを取得して関数に渡すために、何らかの方法でmからを削除できることを意味する必要があります。m tt

ここに私の質問があります:

  • そのようなバインド オペレーター内でのみ可能なものmからを削除する機能です。m tこのバインド オペレーターには特別な特権などがありますか?

  • 状態の変化と何の関係がありますか? モナドの目的は、副作用を「ラップ」して、プログラムの残りの部分から分離することだと私は理解しています (私はそう思います)。しかし、これにおける bind オペレーターの役割は何でしょうか?

4

6 に答える 6

12

「M t」から「M」を削除する機能は、そのようなバインド オペレーター内でのみ可能です。

そのタイプが指定するように、バインド オペレータ内で確かに可能です。

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

モナドの 'run' 関数は、通常、これを行うこともできます (計算から純粋な値を返すため)。

モナドの目的は、副作用を「ラップ」して、プログラムの残りの部分から分離することです。

うーん。いいえ、モナドは計算の概念をモデル化させてくれます。副作用のある計算は、状態、バックトラッキング、継続、並行性、トランザクション、オプションの結果、ランダムな結果、可逆的な状態、非決定性などの概念の 1 つにすぎません。これらはすべてモナドとして記述できます。

IOモナドはあなたが言及しているものだと思います。これは少し変わったモナドです。ワールドの状態に対する抽象的な変化のシーケンスを生成し、ランタイムによって評価されます。Bind は単に IO モナド内で物事を正しい順序で並べることを可能にします -- そしてコンパイラは、これらの順序付けされた世界を変更するすべてのアクションを、マシンのその状態を変更する命令コードに変換します。

ただし、これは一般的なモナドではなく、IO モナドに固有のものです。

于 2009-10-25T16:47:33.310 に答える
10

'M'を'Mt'から削除する機能は、そのようなバインド演算子内でのみ可能です。このバインド演算子には特別な特権などがありますか?

バインドは決して特別な場合ではありませんが、通常はモナドデータ型と同じモジュールで定義されます。したがって、モジュールによってエクスポートされない詳細を認識(および使用)する可能性があります。通常の場合、モジュールはデータ型をエクスポートしますが、コンストラクターや型の内部構造に関するその他の詳細はエクスポートしません。次に、モジュールを使用するコードの場合、データ型の内部動作は表示されず、そのコードはこの型の値を直接変更できません。

モジュール内で定義された関数とは対照的に、たとえばバインド演算子のように、定義されたモジュールから好きな>>=ものにアクセスできます。したがって、そのような関数は「外部」関数ではできないことを実行できる可能性があります。

特別な場合はIOモナドです。これはモジュールによって定義されていないため、ランタイムシステム/コンパイラに組み込まれているためです。ここで、コンパイラはその実装の内部の詳細を認識し、のような関数を公開しIOます>>=。これらの関数の実装は、「プログラムの外部」に存在するため、実際に特別な特権がありますが、これは特殊なケースであり、この事実をHaskell内から観察することはできません。

それは状態の変化と何の関係がありますか?私は、モナドの目標が副作用を「ラップ」して、プログラムの他の部分から分離することであることを理解しています(私は思います)。しかし、これにおけるバインド演算子の役割は何ですか?

状態の変化に関係する必要はありません。これは、moandsで処理できる問題の1つにすぎません。モナドは、IOIOを特定の順序で実行するために使用されますが、通常、モナドは関数を組み合わせる方法にすぎません。

一般に、モナド(具体的にはバインド関数)は、特定の関数をより大きな関数にまとめる方法を定義します。関数を組み合わせるこの方法は、モナドで抽象化されています。この結合がどのように正確に機能するか、またはそのような方法で関数を結合する理由は重要ではありません。モナドは、特定の関数を特定の方法で結合する方法を指定するだけです。(この「C#プログラマーのためのモナド」の回答も参照してください。基本的には、例を挙げて数回繰り返します。)

于 2009-10-25T20:58:58.810 に答える
5

以下は type-class の定義ですMonad

class  Monad m  where

    (>>=)       :: forall a b. m a -> (a -> m b) -> m b
    (>>)        :: forall a b. m a -> m b -> m b
    return      :: a -> m a
    fail        :: String -> m a

    m >> k      = m >>= \_ -> k
    fail s      = error s

type-class の各 type-in Monad​​stance は、独自の>>=関数を定義します。type-in​​stance の例を次に示しますMaybe

instance  Monad Maybe  where

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

    (Just _) >>  k      = k
    Nothing  >>  _      = Nothing

    return              = Just
    fail _              = Nothing

ご覧のとおり、 のMaybeバージョンは型インスタンス>>=を理解するために特別に定義されており、データ コンストラクターおよびへの正当なアクセスが可能な場所で定義されているため、 のバージョンは のをアンラップして渡すことができます。それらを介して。Maybedata Maybe aNothingJust aMaybe>>=aMaybe a

例を見ていくには、次のようにします。

x :: Maybe Integer
x = do a <- Just 5
       b <- Just (a + 1)
       return b

脱糖すると、do 記法は次のようになります。

x :: Maybe Integer
x = Just 5        >>= \a ->
    Just (a + 1)  >>= \b ->
    Just b

次のように評価されます。

  =                  (\a ->
    Just (a + 1)  >>= \b ->
    Just b) 5

  = Just (5 + 1)  >>= \b ->
    Just b

  =                  (\b ->
    Just b) (5 + 1)

  = Just (5 + 1)

  = Just 6
于 2009-10-26T01:52:04.643 に答える
4

タイプは、おかしなことに、十分に並んでいます。方法は次のとおりです。

モナドはファンクターでもあることを忘れないでください。次の関数は、すべてのファンクターに対して定義されています。

fmap :: (Functor f) => (a -> b) -> f a -> f b

さて、質問:これらのタイプは本当に並んでいますか?はい、そうです。aからへの関数が与えられたb場合、利用可能な環境fがあるa場合、利用可能な環境fbあります。

三段論法との類推による:

(Functor Socrates) => (Man -> Mortal) -> Socrates Man -> Socrates Mortal

さて、ご存知のように、モナドはバインドとリターンを備えたファンクターです。

return :: (Monad m) => a -> m a
(=<<) :: (Monad m) => (a -> m b) -> m a -> m b

あなたはそれを同等に知らないかもしれません、それはリターンと参加を備えたファンクターです:

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

を剥がしている様子をご覧くださいm。モナドmを使用すると、からに移動できるとは限りませんが、からm aに移動するaことはできます。m (m a)m a

次に、の最初の引数を見てください(=<<)。これはタイプの関数です(a -> m b)。その関数をに渡すとどうなりますfmapか?あなたは得るm a -> m (m b)m aしたがって、関数を使用して「マッピング」すると、a -> m bが得られますm (m b)。これは、の引数のタイプとまったく同じであることに注意してくださいjoin。これは偶然ではありません。「バインド」の適切な実装は次のようになります。

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

実際、バインドと結合は相互に定義できます。

join = (>>= id)
于 2009-10-26T03:08:06.910 に答える
2

モナドの目的は、副作用を「ラップ」して、プログラムの残りの部分から分離することだと私は理解しています (私はそう思います)。

実際にはそれよりももう少し微妙です。モナドを使用すると、順序付けを非常に一般的な方法でモデル化できます。ドメインの専門家と話すと、「最初に X を試し、次に Y を試し、それがうまくいかない場合は Z を試す」というようなことをよく言います。そのようなものを従来の言語で実装しようとすると、それが適合しないことがわかります。そのため、ドメインの専門家が「それから」という言葉で意味するものをカバーするために、余分なコードをたくさん書かなければなりません。

Haskell では、「then」をバインド演算子に変換したモナドとしてこれを実装できます。たとえば、特定のルールに従ってプールからアイテムを割り当てる必要があるプログラムを書いたことがあります。ケース 1 の場合、プール X から取得しました。それが空の場合は、プール Y に移動しました。ケース 2 の場合、プール Y から直接取得する必要がありました。プール X または Y のいずれかで最も使用頻度の低いもの。私はそのジョブ専用のカスタム モナドを作成して、次のように記述できるようにしました。

case c of
   1: do {try poolX; try poolY}
   2: try poolY
   3: try $ lru [poolX, poolY]

とてもうまくいきました。

もちろん、これにはシーケンシングの従来のモデルも含まれます。IO モナドは、他のすべてのプログラミング言語が持つモデルです。Haskell では、環境の一部ではなく、明示的な選択です。ST モナドは IO のメモリ ミューテーションを提供しますが、実際の入出力はありません。一方、 State モナドを使用すると、状態を名前付き型の単一の値に制限できます。

本当に脳が曲がる何かについては、後方状態モナドに関するこのブログ投稿を参照してください。状態は「実行」とは逆方向に伝播します。これを、1 つの命令に続いて次の命令を実行する状態モナドのようなものと考えると、「put」は状態値を先行する「get」に時間をさかのぼって送信します。実際に起こることは、パラドックスがない場合にのみ終了する相互再帰関数がセットアップされることです。そのようなモナドをどこで使用するかはよくわかりませんが、モナドが計算のモデルであることの要点を示しています。

その準備ができていない場合は、 bind をオーバーロード可能なセミコロンと考えてください。それはあなたをかなり長い道のりに導きます。

于 2009-10-28T21:26:56.657 に答える
2

読むことを強くお勧めします ( http://blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html )。モナドが存在する完全で常識的な理由を与えてくれます。

于 2009-10-26T01:34:10.467 に答える