モナドを見るさまざまな方法


29

Haskellの学習中に、モナドとは何か、Haskellでモナドが重要である理由を説明しようとする多くのチュートリアルに直面しました。それぞれが類推を使用していたので、意味をつかみやすくなります。結局のところ、私はモナドが何であるかの3つの異なるビューになりました:

表示1:ラベルとしてのMonad

時々、モナドは特定のタイプのラベルとして考えます。たとえば、次のタイプの関数:

myfunction :: IO Int

myfunctionは、実行されるたびにInt値を生成する関数です。結果の型はIntではなくIO Intです。そのため、IOは、Int値がIOアクションが行われたプロセスの結果であることをユーザーに警告するInt値のラベルです。

その結果、このInt値はIOを持つプロセスからの値としてマークされているため、この値は「ダーティ」です。あなたのプロセスはもはや純粋ではありません。

見解2:モナドは、厄介なことが起こりうるプライベートな空間として。

すべてのプロセスが純粋で厳格なシステムでは、副作用が必要になる場合があります。そのため、モナドは、厄介な副作用を実行するための小さなスペースです。この空間では、あなたは純粋な世界から脱出し、不純なものに行き、あなたのプロセスを作り、価値を持って戻ってくることができます。

表示3:カテゴリー理論のようなモナド

これは私が完全に理解していない見解です。モナドは、同じカテゴリーまたはサブカテゴリーの単なるファンクターです。たとえば、Int値があり、サブカテゴリとしてIO Intがあります。これは、IOプロセスの後に生成されるInt値です。

これらのビューは正しいですか?どちらがより正確ですか?


5
#2は一般的にモナドではありません。実際、これはIOにほとんど制限されており、有用なビューではありません(モナドではないものを参照)。また、「厳格」は一般にHaskell 所有しないプロパティに名前を付けるために使用されます(厳密な評価)。ちなみに、モナドもそれを変更しません(繰り返しますが、モナドではないものを参照してください)。

3
技術的には、3番目のものだけが正しいです。モナドはエンドファンクターであり、特別な操作からプロモーションとバインドまでが定義されています。モナドは多数あります-リストモナドは、モナドの背後にある直感を理解するための完璧な例です。readS機能はさらに優れています。驚くべきことに、モナドは純粋な関数型言語の状態を暗黙的にスレッド化するツールとして使用できます。これはモナドの定義プロパティではありません。偶然であり、状態スレッドはそれらの用語で実装される可能性があります。IOにも同じことが当てはまります。
-permeakra

Common Lispには、言語の一部として独自のコンパイラがあります。Haskellにはモナドがあります。
ネス

回答:


33

ビュー#1と#2は一般に正しくありません。

  1. あらゆる種類のデータ型が* -> *ラベルとして機能し、モナドはそれ以上のものです。
  2. IOモナドを除く)モナド内の計算は不純ではありません。それらは、副作用があると私たちが考える計算を単に表していますが、それらは純粋です。

これらの誤解はどちらもIO、実際には少し特別なモナドに焦点を当てることに由来しています。

可能な場合はカテゴリー理論に入らずに、#3について少し詳しく説明します。


標準計算

関数型プログラミング言語でのすべての計算は、ソースタイプとターゲットタイプを持つ関数として表示できますf :: a -> b。関数が複数の引数を持っている場合、カリー化することで引数が1つの関数に変換できますHaskell wikiも参照)。また、値x :: a(引数が0の関数)だけがある場合は、その値をユニットタイプの引数を取る関数に変換できます(\_ -> x) :: () -> a

.演算子を使用してこのような関数を構成することにより、より複雑なプログラムをより単純なプログラムから作成できます。たとえば、がf :: a -> bありg :: b -> c、取得する場合g . f :: a -> c。これは、変換された値に対しても機能することに注意してください。x :: a表現に変換して変換すると、が得られf . ((\_ -> x) :: () -> a) :: () -> bます。

この表現には、いくつかの非常に重要なプロパティがあります。

  • 非常に特殊な関数、つまり各タイプの恒等関数id :: a -> aがありますa。それは同一要素 に対して.fの両方に等しいf . idとしますid . f
  • 関数合成演算子.結合的です。

モナド計算

結果に単一の戻り値以外のものが含まれる特別なカテゴリの計算を選択して操作したいとします。「もっと何か」が何を意味するのかを指定するのではなく、できるだけ一般的なものにしたいのです。「もっと何か」を表現する最も一般的な方法は、それを型関数- m一種の型* -> *(つまり、ある型を別の型に変換する)として表現することです。したがって、処理したい計算の各カテゴリに対して、いくつかの型関数がありますm :: * -> *。(中ハスケル、mである[]IOMaybe、など)とカテゴリ意志は種類のすべての機能が含まれていますa -> m b

ここで、基本的な場合と同じ方法で、そのようなカテゴリーの関数を操作したいと思います。これらの機能を構成できるようにしたい、構成を連想的にしたい、そしてアイデンティティを持ちたいです。必要なもの:

  • <=<関数をのようなものに合成する演算子(呼び出しましょう)をf :: a -> m b持ちます。そして、それは連想的でなければなりません。g :: b -> m cg <=< f :: a -> m c
  • 各タイプに何らかのアイデンティティ関数を持たせるために、それを呼び出しましょうreturn。我々はまた、それが欲しいf <=< returnと同じであるfと同じreturn <=< f

そのm :: * -> *ような関数がreturnあり<=<モナドと呼ばれるもの。基本的な場合と同様に、より単純な計算から複雑な計算を作成できますが、戻り値の型はによって変換されmます。

(実際、ここではカテゴリという用語を少し乱用しました。カテゴリ理論の意味では、これらの法律に従っていることがわかった後にのみ、構築をカテゴリと呼ぶことができます。)

ハスケルのモナド

Haskell(および他の関数型言語)では、型の関数ではなく、ほとんどの場合値を操作します() -> a。したがって<=<、各モナドに対して定義する代わりに、functionを定義します(>>=) :: m a -> (a -> m b) -> m b。このような代替の定義は同等であり、>>=使用すること<=<でその逆を表すことができます(演習として試すか、ソースを参照してください)。原理は今ではあまり明らかではありませんが、同じままです。結果は常に型でm aあり、型の関数を構成しますa -> m b

作成する各モナドについて、それを確認しreturn<=<必要なプロパティである結合性と左右の同一性を確認することを忘れてはなりません。使用return>>=て表現され、それらはモナド則と呼ばれます

例-リスト

あることを選択mした場合[]、型の関数のカテゴリを取得しますa -> [b]。このような関数は非決定的な計算を表し、その結果は1つ以上の値になる可能性がありますが、値はありません。これにより、いわゆるリストモナドが生成されます。構成f :: a -> [b]g :: b -> [c]機能は次のとおりですg <=< f :: a -> [c]。typeのすべての可能な結果を​​計算し、それぞれに[b]適用gし、すべての結果を単一のリストに収集することを意味します。Haskellで表現

return :: a -> [a]
return x = [x]
(<=<) :: (b -> [c]) -> (a -> [b]) -> (a -> [c])
g (<=<) f  = concat . map g . f

またはを使用して >>=

(>>=) :: [a] -> (a -> [b]) -> [b]
x >>= f  = concat (map f x)

この例では、戻り[a]値の型があり、typeの値が含まれていない可能性があることに注意してくださいa。実際、戻り値の型がそのような値を持つべきであるというモナドの要件はありません。一部のモナドには常に(IOまたはのようなState)がありますが、一部のモナドにはない、[]またはのようなものがありますMaybe

IOモナド

私が述べたように、IOモナドはやや特別です。タイプの値とは、プログラムの環境と対話することによって構築されるタイプの値をIO a意味aます。そのため(他のすべてのモナドとは異なり)、IO a純粋な構造を使用して型の値を記述することはできません。以下IOは、環境と相互作用する計算を区別するタグまたはラベルです。これは(唯一の場合)ビュー#1と#2が正しい場合です。

以下のためのIOモナド:

  • 組成物f :: a -> IO bおよびg :: b -> IO c方法:計算f環境と相互作用し、その次に、計算g値を使用し、環境との相互作用の結果を計算します。
  • returnIO値に「タグ」を追加するだけです(環境を損なわずに結果を「計算」するだけです)。
  • モナドの法則(結合性、同一性)はコンパイラーによって保証されています。

いくつかのメモ:

  1. モナド計算の結果タイプは常にであるためm aIOモナドから「エスケープ」する方法はありません。意味は次のとおりです。いったん計算が環境と相互作用すると、それから計算を構築することはできません。
  2. 関数型プログラマーが純粋な方法で何かを作成する方法を知らない場合、(最後の手段として)IOモナド内のステートフルな計算によってタスクをプログラムできます。これがIOしばしばプログラマーのsin binと呼ばれる理由です。
  3. (関数型プログラミングの意味での)不純な世界では、値を読み取ると環境も変わる可能性があることに注意してください(ユーザーの入力を消費するなど)。そのため、関数のようなgetChar結果の型はである必要がありIO somethingます。

3
素晴らしい答え。IO言語の観点から特別なセマンティクスがないことを明確にします。これは特別なものではなく、他のコードと同じように動作します。ランタイムライブラリの実装のみが特別です。また、エスケープする特別な方法があります(unsafePerformIO)。人々はしばしばIO特別な言語要素または宣言タグと考えるので、これは重要だと思います。そうではない。
usr

2
@usr良い点。unsafePerformIOは本当に安全ではないので、専門家のみが使用する必要があることを付け加えます。すべてを壊すことができます。たとえば、coerce :: a -> b2つのタイプを変換する関数を作成できます(ほとんどの場合、プログラムをクラッシュさせます)。この例を参照してください-関数をInt等に変換することもできます
ペトルプドラク

もう1つの「特別な魔法」モナドはSTです。これにより、必要に応じて読み書きできるメモリへの参照を宣言でき(モナド内でのみ)、次を呼び出して結果を抽出できますrunST :: (forall s. GHC.ST.ST s a) -> a
sara

5

表示1:ラベルとしてのMonad

「その結果、このInt値はIOを持つプロセスからの値としてマークされているため、この値は「ダーティ」です。」

「IO Int」は一般にInt値ではありません(ただし、「return 3」などの場合もあります)。Int値を出力するプロシージャです。この「プロシージャ」の異なる実行は、異なるInt値を生成する場合があります。

モナドmは、組み込み(必須)の「プログラミング言語」です。この言語内では、「手順」を定義することができます。(ma型の)モナド値は、a型の値を出力するこの「プログラミング言語」の手順です。

例えば:

foo :: IO Int

Int型の値を出力するプロシージャです。

次に:

bar :: IO (Int, Int)
bar = do
  a <- foo
  b <- foo
  return (a,b)

2つの(おそらく異なる)Intを出力するプロシージャです。

そのようなすべての「言語」はいくつかの操作をサポートします。

  • 2つのプロシージャ(maおよびmb)を「連結」することができます。最初のプロシージャと2番目のプロシージャで構成される、より大きなプロシージャ(ma >> mb)を作成できます。

  • さらに、最初の出力(a)が2番目の出力(ma >> = \ a-> ...)に影響を与える可能性があります。

  • プロシージャ(xを返す)は、一定の値(x)を生成する場合があります。

さまざまな組み込みプログラミング言語は、サポートする種類によって異なります。たとえば、次のとおりです。

  • ランダムな値を生成します。
  • 「分岐」([]モナド);
  • 例外(スロー/キャッチ)(どちらかのeモナド);
  • 明示的な継続/ callccサポート。
  • 他の「エージェント」へのメッセージの送受信。
  • 変数の作成、設定、読み取り(このプログラミング言語のローカル)(STモナド)。

1

モナド型とモナドクラスを混同しないでください。

モナド型(つまり、モナドクラスのインスタンスである型)は、特定の問題を解決します(原則として、各モナド型は異なる問題を解決します):State、Random、Maybe、IO。それらはすべて、コンテキストを持つタイプです(「ラベル」と呼ばれるものですが、それがモナドになるわけではありません)。

それらすべてについて、「選択を伴う操作の連鎖」の必要性があります(1つの操作は前の結果に依存します)。ここにモナドクラスが登場します。型(特定の問題を解決する)をモナドクラスのインスタンスにし、連鎖の問題を解決します。

モナドクラスが何を解決するかをご覧ください

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.