ビュー#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
である[]
、IO
、Maybe
、など)とカテゴリ意志は種類のすべての機能が含まれていますa -> m b
。
ここで、基本的な場合と同じ方法で、そのようなカテゴリーの関数を操作したいと思います。これらの機能を構成できるようにしたい、構成を連想的にしたい、そしてアイデンティティを持ちたいです。必要なもの:
<=<
関数をのようなものに合成する演算子(呼び出しましょう)をf :: a -> m b
持ちます。そして、それは連想的でなければなりません。g :: b -> m c
g <=< 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
値を使用し、環境との相互作用の結果を計算します。
return
IO
値に「タグ」を追加するだけです(環境を損なわずに結果を「計算」するだけです)。
- モナドの法則(結合性、同一性)はコンパイラーによって保証されています。
いくつかのメモ:
- モナド計算の結果タイプは常にであるため
m a
、IO
モナドから「エスケープ」する方法はありません。意味は次のとおりです。いったん計算が環境と相互作用すると、それから計算を構築することはできません。
- 関数型プログラマーが純粋な方法で何かを作成する方法を知らない場合、(最後の手段として)
IO
モナド内のステートフルな計算によってタスクをプログラムできます。これがIO
しばしばプログラマーのsin binと呼ばれる理由です。
- (関数型プログラミングの意味での)不純な世界では、値を読み取ると環境も変わる可能性があることに注意してください(ユーザーの入力を消費するなど)。そのため、関数のような
getChar
結果の型はである必要がありIO something
ます。