型にインスタンスがある場合、型m :: * -> *
を持つMonad
関数のチューリング完全な構成が得られますa -> m b
。これは非常に便利なプロパティです。特定の意味から離れたさまざまなチューリング完全な制御フローを抽象化する機能が得られます。これは、制御フローをサポートする型を操作するための制御フローの抽象化をサポートする最小限の構成パターンです。
Applicative
たとえば、これを と比較してください。そこでは、プッシュダウン オートマトンと同等の計算能力を持つ合成パターンのみが得られます。もちろん、より多くの型がより制限された力で構成をサポートすることは事実です。利用可能な電力を制限すると、追加の最適化を行うことができるのは事実です。この 2 つの理由により、このApplicative
クラスが存在し、有用です。ただし、通常はインスタンスになる可能性があるMonad
ため、型のユーザーはその型で可能な最も一般的な操作を実行できます。
編集:Monad
一般的な要求により、クラス
を使用するいくつかの関数を次に示します。
ifM :: Monad m => m Bool -> m a -> m a -> m a
ifM c x y = c >>= \z -> if z then x else y
whileM :: Monad m => (a -> m Bool) -> (a -> m a) -> a -> m a
whileM p step x = ifM (p x) (step x >>= whileM p step) (return x)
(*&&) :: Monad m => m Bool -> m Bool -> m Bool
x *&& y = ifM x y (return False)
(*||) :: Monad m => m Bool -> m Bool -> m Bool
x *|| y = ifM x (return True) y
notM :: Monad m => m Bool -> m Bool
notM x = x >>= return . not
これらを do 構文 (または raw 演算子) と組み合わせると、>>=
名前のバインド、無限ループ、および完全なブール論理が得られます。これは、チューリングに完全性を与えるのに十分なプリミティブのよく知られたセットです。すべての関数が、単純な値ではなくモナド値で機能するように持ち上げられていることに注意してください。すべてのモナド効果は必要な場合にのみバインドされます - の選択されたブランチからの効果のみがifM
最終的な値にバインドされます。可能であれば、 と の両方が 2 番目の引数*&&
を*||
無視します。等々..
現在、これらの型シグネチャは、すべてのモナド オペランドの関数を含んでいるわけではありませんが、それは単なる認識上の単純化です。関数以外のすべての引数と結果が に変更された場合、bottoms を無視して意味上の違いはありません() -> m a
。その認知オーバーヘッドを最適化することは、ユーザーにとってより使いやすいです。
Applicative
では、インターフェイスを使用してこれらの関数に何が起こるかを見てみましょう。
ifA :: Applicative f => f Bool -> f a -> f a -> f a
ifA c x y = (\c' x' y' -> if c' then x' else y') <$> c <*> x <*> y
ええと。同じ型シグネチャを取得しました。しかし、ここにはすでに大きな問題があります。x と y の両方の効果は、どちらの値が選択されても、構成された構造にバインドされます。
whileA :: Applicative f => (a -> f Bool) -> (a -> f a) -> a -> f a
whileA p step x = ifA (p x) (whileA p step <$> step x) (pure x)
ええと、それは問題ないように思えますが、ifA
常に両方のブランチを実行するため、無限ループであるという事実を除けば...それほど近くさえないことを除いて. pure x
タイプはf a
です。whileA p step <$> step x
タイプはf (f a)
です。これは無限ループでさえありません。コンパイルエラーです。もう一度試してみましょう..
whileA :: Applicative f => (a -> f Bool) -> (a -> f a) -> a -> f a
whileA p step x = ifA (p x) (whileA p step <*> step x) (pure x)
さて撃て。そこまで行かないでください。whileA p step
タイプはa -> f a
です。これを の最初の引数として使用しようとすると、最上位の型コンストラクター (ではなく) のインスタンスが<*>
取得されます。うん、これも仕方ない。Applicative
(->)
f
実際、このインターフェースでMonad
動作する私の例の唯一の関数は. 実際、その特定の機能はインターフェイスだけでうまく機能します。残り?彼らは失敗します。Applicative
notM
Functor
もちろん、Monad
インターフェイスではできないコードを、インターフェイスを使用して記述できることが期待されApplicative
ます。結局のところ、厳密にはより強力です。しかし興味深いのは、あなたが失うものです。入力に基づいてどのような効果をもたらすかを変更する関数を構成する能力を失います。つまり、関数を types で構成する特定の制御フロー パターンを作成できなくなりますa -> f b
。
チューリング完全な構成は、まさにMonad
インターフェイスを面白くするものです。チューリング完全な構成が許可されていない場合、プログラマーであるあなたが、IO
適切に事前にパッケージ化されていない特定の制御フローでアクションを一緒に構成することは不可能です。Monad
プリミティブを使用して任意の制御フローを表現できるという事実が、IO
型を Haskell で IO 問題を管理するための実行可能な方法にしたのです。
IO
意味的に有効なMonad
インターフェイスを持つだけでなく、さらに多くの型があります。Haskell には、インターフェース全体を抽象化するための言語機能があります。これらの要因によりMonad
、可能であればインスタンスを提供する価値のあるクラスです。そうすることで、具象型が何であるかに関係なく、モナド型を操作するために提供されているすべての既存の抽象機能にアクセスできます。
したがって、Haskell プログラマーが型のインスタンスを常に気にかけているように見えるMonad
のは、それが提供できる最も一般的に有用なインスタンスだからです。