質問
モナドについて勉強しているのですが、いくつか質問があります。
これが今私がいる場所です。私が間違っているところを修正してください。
の
>>=
シンボルは中置演算子です。中置演算子は、2 つの引数 (左側と右側) を受け取り、値を返す関数です。の
>>=
シンボルはバインド演算子と呼ばれ、署名がありますMonad m => m t -> (t -> m u) -> m u
. 。ただし、ここでは種類が揃っていないようです。type の値を取得しますm t
2 番目の引数は、t
. 。(点と点を結ぶ方法がわかりません。)これは、bind 関数が何らかの方法で、
m
からm t
を得るためにt
そしてそれを関数に渡します。
私の質問は次のとおりです。
を取り除く能力ですか?
m
からm t
これは、このようなバインド演算子の内部でのみ可能です。このバインド演算子には特別な権限か何かがあるのでしょうか?それは状態の変化とどのような関係があるのでしょうか?モナドの目的は副作用を「ラップ」してプログラムの残りの部分から分離することであると私は理解しています(私はそう思っています)。しかし、この場合のバインド演算子の役割は何でしょうか?
解決
「M t」から「M」を削除する機能は、そのようなバインド演算子内でのみ可能です。このバインド演算子には特別な特権や何かがありますか?
バインドは決して特別な場合ではありませんが、通常はモナドのデータ型と同じモジュールで定義されます。したがって、モジュールによってエクスポートされない詳細について知っている(使用する)場合があります。通常、モジュールはデータ型をエクスポートしますが、コンストラクターや型の内部構造に関するその他の詳細はエクスポートしません。次に、モジュールを使用するコードの場合、データ型の内部動作は見えず、そのコードはこの型の値を直接変更できません。
モジュール内で定義された関数(たとえば、バインド演算子>>=
など)とは反対に、定義されているモジュールから好きなものにアクセスできます。したがって、そのような関数は<!> quot; external <!> quot;関数は実行できません。
特殊なケースはIO
モナドです。これはモジュールによって定義されていないが、ランタイムシステム/コンパイラに組み込まれているためです。ここで、コンパイラはその実装の内部詳細を認識し、<=>の<=>などの関数を公開します。これらの関数の実装は、実際には<!> quot;プログラムの外部<!> quot;にあるため、特別な特権がありますが、これは特別なケースであり、この事実はHaskell内からは観察できません。
状態の変化とは何の関係がありますか?モナドの目的は、副作用を「ラップ」して、プログラムの残りの部分から隔離することだと思います(と思います)。しかし、これにおけるバインド演算子の役割は何ですか?
状態の変更を行う必要はありません。これは、moandで処理できる問題の1つにすぎません。 <=>モナドは特定の順序でIOを実行するために使用されますが、一般的にモナドは関数を結合する方法にすぎません。
一般にモナド(具体的にはバインド関数)は、特定の関数を組み合わせてより大きな関数にする方法を定義します。関数を結合するこの方法は、モナドで抽象化されています。この結合が正確にどのように機能するのか、なぜそのような方法で関数を結合するのかは重要ではありません。モナドは特定の関数を特定の方法で結合する方法を指定するだけです。 ( this <!> quot; Monads for C#も参照してください。プログラマー<!> quot; answer 基本的に例を数回繰り返します)
他のヒント
は、「M t」から「M」を削除する機能で、このようなバインド演算子内でのみ可能です。
まあ、そのタイプが指定するように、それは確かにバインド演算子内で可能です:
(>>=) :: m a -> (a -> m b) -> m b
通常、モナドの「run」関数もこれを行うことができます(計算から純粋な値を返すため)。
モナドの目的は、副作用を「ラップ」して、プログラムの他の部分から隔離することです
うーん。いいえ、モナドでは計算の概念をモデル化できます。副作用の計算は、状態、バックトラッキング、継続、同時実行性、トランザクション、オプションの結果、ランダムな結果、復帰可能な状態、非決定性などの概念の1つに過ぎません...すべてモナドとして記述できます
IOモナドは、あなたが言及しているものだと思います。それは少し奇妙なモナドです-それは世界の状態に対する抽象的な変化のシーケンスを生成し、それがランタイムによって評価されます。 BindはIOモナドで物事を正しい順序で配列するだけで、コンパイラはこれらの配列された世界を変更するすべてのアクションを、マシンの状態を変更する命令型コードに変換します。
これはIOモナドに非常に固有のものですが、一般的なモナドではありません。
以下は型クラスの定義です。 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
型クラスの各型インスタンス Monad
独自の定義をする >>=
関数。これは type-instance の例です Maybe
:
instance Monad Maybe where
(Just x) >>= k = k x
Nothing >>= _ = Nothing
(Just _) >> k = k
Nothing >> _ = Nothing
return = Just
fail _ = Nothing
ご覧のとおり、 Maybe
のバージョン >>=
を理解するために特別に定義されています Maybe
type-instance に合法的にアクセスできる場所で定義されているため、 data Maybe a
データコンストラクター Nothing
そして Just a
, 、 Maybe
のバージョン >>=
を解くことができます a
が入っています Maybe 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
おもしろいことに、型は並んでいます。方法は次のとおりです。
モナドもファンクターであることを思い出してください。次の関数はすべてのファンクターに対して定義されています:
fmap :: (Functor f) => (a -> b) -> f a -> f b
次の質問:これらのタイプは本当に並んでいますか?はい、そうです。 a
からb
までの関数を考えると、f
が利用可能な環境m
がある場合、m a
が利用可能な環境m (m a)
があります。
三段論法と同様に:
(Functor Socrates) => (Man -> Mortal) -> Socrates Man -> Socrates Mortal
今、あなたが知っているように、モナドはバインドとリターンを備えたファンクターです:
return :: (Monad m) => a -> m a
(=<<) :: (Monad m) => (a -> m b) -> m a -> m b
同等に知らないかもしれませんが、これはreturnとjoinを備えたファンクターです:
join :: (Monad m) => m (m a) -> m a
(=<<)
の剥がし方を確認してください。モナド(a -> m b)
では、常にfmap
からm a -> m (m b)
に到達できるわけではありませんが、常にa -> m b
からm (m b)
に到達できます。
join
の最初の引数を見てください。タイプ<=>の関数です。その関数を<=>に渡すとどうなりますか? <=>を取得します。したがって、<!> quot; mapping <!> quot; <=>に関数<=>を重ねると、<=>が得られます。これは、<=>への引数のタイプとまったく同じであることに注意してください。これは偶然ではありません。 <!> quot; bind <!> quot;の合理的な実装。次のようになります。
(>>=) :: m a -> (a -> m b) -> m b
x >>= f = join (fmap f x)
実際には、バインドと結合は相互に関連して定義できます。
join = (>>= id)
( http://blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html )。モナドが存在する完璧な常識の理由を示します。
モナドの目的は、副作用を「ラップ」して、プログラムの残りの部分から隔離することだと思います(私は思う)。
実際にはそれよりも少し微妙です。モナドを使用すると、シーケンスを非常に一般的な方法でモデル化できます。多くの場合、ドメインエキスパートと話すと、<!> quot;まずXを試します。次にYを試し、それがうまくいかない場合はZ <!> quot;を試します。そのようなものを従来の言語で実装するようになると、それが収まらないことがわかるので、<!> quot; then <!> quotという言葉が意味するドメインの専門家をカバーするために、多くの余分なコードを書く必要があります;。
Haskellでは、これを<!> quot; then <!> quot;のモナドとして実装できます。バインド演算子に変換されます。そのため、たとえば、特定のルールに従ってプールからアイテムを割り当てる必要があるプログラムを書いたことがあります。ケース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モナドでは、名前付き型の単一の値に状態を制限できます。
本当に脳が曲がっているものについては、をご覧ください。後方状態モナドに関するこのブログ投稿。状態は、<!> quot; execution <!> quot;と反対方向に伝播します。これを、ある命令に続いて次の命令を実行する状態モナドと考えると、<!> quot; put <!> quot;状態値を前の<!> quot; get <!> quot;に逆方向に送信します。実際には、 発生するのは、矛盾が存在しない場合にのみ終了する相互再帰関数が設定されることです。どこでそのようなモナドを使うべきかはよくわかりませんが、モナドが計算のモデルであることのポイントを示しています。
準備ができていない場合は、バインドをオーバーロード可能なセミコロンと考えてください。それはあなたにかなり長い道のりを与えます。