28

モナドは、(純粋な) 関数型プログラミング、基本的には Haskell で頻繁に使用される数学的構造です。ただし、アプリカティブ ファンクター、ストロング モナド、モノイドなど、利用可能な他の多くの数学的構造があります。より具体的なものもあれば、より一般的なものもあります。しかし、モナドの方がはるかに人気があります。何故ですか?

私が思いついた 1 つの説明は、それらは一般性と特異性の間のスイート スポットであるということです。これは、モナドがデータに関する十分な仮定を取り込んで、私たちが通常使用するアルゴリズムを適用し、通常持っているデータがモナドの法則を満たしていることを意味します。

別の説明として、Haskell はモナドの構文 (do 記法) を提供するが、他の構造体の構文は提供しないということも考えられます。つまり、Haskell プログラマー (および関数型プログラミングの研究者) は直感的にモナドに惹かれます。より一般的または具体的な (効率的な) 関数があれば、同様に動作します。

4

6 に答える 6

29

この 1 つの特定の型クラス ( Monad) が他の多くの型クラスよりも過度に注目されているのは、主に歴史的なまぐれではないかと思います。人々はしばしば を連想IOMonadますが、この 2 つは独立して有用なアイデアです (リストの反転やバナナと同様)。IOはマジカル (実装はあるが意味がない) であり、 にMonad関連付けられることが多いためIO、 についてマジカルな考え方に陥りがちMonadです。

(余談ですIOが、 even がモナドかどうかは疑問です。モナドの法則は成立しますか? 法則はに対して何を意味IOするのでしょうか? つまり、等値とは何を意味するのでしょうか?状態モナドとの問題のある関連付けに注意してください。)

于 2013-05-08T15:40:28.563 に答える
17

型にインスタンスがある場合、型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動作する私の例の唯一の関数は. 実際、その特定の機能はインターフェイスだけでうまく機能します。残り?彼らは失敗します。ApplicativenotMFunctor

もちろん、Monadインターフェイスではできないコードを、インターフェイスを使用して記述できることが期待されApplicativeます。結局のところ、厳密にはより強力です。しかし興味深いのは、あなたが失うものです。入力に基づいてどのような効果をもたらすかを変更する関数を構成する能力を失います。つまり、関数を types で構成する特定の制御フロー パターンを作成できなくなりますa -> f b

チューリング完全な構成は、まさにMonadインターフェイスを面白くするものです。チューリング完全な構成が許可されていない場合、プログラマーであるあなたが、IO適切に事前にパッケージ化されていない特定の制御フローでアクションを一緒に構成することは不可能です。Monadプリミティブを使用して任意の制御フローを表現できるという事実が、IO型を Haskell で IO 問題を管理するための実行可能な方法にしたのです。

IO意味的に有効なMonadインターフェイスを持つだけでなく、さらに多くの型があります。Haskell には、インターフェース全体を抽象化するための言語機能があります。これらの要因によりMonad、可能であればインスタンスを提供する価値のあるクラスです。そうすることで、具象型が何であるかに関係なく、モナド型を操作するために提供されているすべての既存の抽象機能にアクセスできます。

したがって、Haskell プログラマーが型のインスタンスを常に気にかけているように見えるMonadのは、それが提供できる最も一般的に有用なインスタンスだからです。

于 2013-05-08T18:17:32.193 に答える