2013-05-08 9 views
25

モナドは、(純粋な)関数型プログラミングでは、基本的にハスケルで使用されている数学的な構造です。しかし、利用可能なファンクタ、強いモナド、またはモンゴイドのような他の多くの数学的構造が利用可能である。より具体的なものもあれば、more genericのものもあります。しかし、モナドははるかに一般的です。何故ですか?Monadsについて特別なものは何ですか?

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

ハスケルのプログラマ(したがって関数プログラミング研究者)がモナドに直感的に描かれていることを意味する他の構造ではなく、モナド(ドゥーノーメイション)効率的な)機能も同様に機能します。

+10

あなたの前提についてはわかりません。私が知っている高度なハスケラーの大半は、ある程度の賛成を得ています。私は確かにしています。人々はまた、まったく無関係な仕事のために、常にモノイドを使用します。カテゴリはまた、しばらくの間、それ自身で出てくる。どんな理由でも、モナスはハスケルではない回路で多くの報道陣を手に入れます。 –

+1

@TikhonJelvis:私は、「私が知っているより高度なハスケラーの大半は、ある程度アプリケーションを支持している」とは思わない。私が最も注目を集めていることは、特に応用に関連するものではありません。例として、私が働いているもの(http://hackage.haskell.org/package/free-operational)を使うために、フリーのモナドは無料のアプリよりももっと興味を持っていると私は判断します。しかし最近では無料申請への関心が高まっている。 –

+5

私が思い出す限り、モンドはWadlerによって一般化されました。その時、退屈なCPSや明示的な状態渡しなしの解析をせずにIOを行うというアイデアは大きなセールスポイントでした。それは非常にエキサイティングな時でした。 A.F.A.I.R.、Haskellはコンストラクタークラスを行っていませんでしたが、Gofer(Hugsの父)が行いました。 Wadlerは、モナドのリスト理解をオーバーロードすることを提案したので、後で表記が来た。 IOがモナドになった後、モナドは初心者のために大きな役割を果たしました。できるだけ多くの方が申し込みがあり、より一般的な矢印はありますが、後で来て、IOはモナドを売るのが大変です。 – AndrewC

答えて

28

この1つの特定の種類のクラス(Monad)に与えられている過度の大きな注意が、主に歴史的な流行であると思われます。人々は多くの場合、IOMonadに関連付けますが、2つは独立して有用なアイデアです(as are list reversal and bananas)。 IOは魔法(実装はあるが、表記なし)で、Monadは多くの場合IOに関連付けられているため、Monadについては魔法の考え方に陥りやすいです。

(脇:。??。それはIOもモナドであるかどうかは疑問だモナドの法則が成り立つかさえIOためを何を意味する法律を行う、すなわち、平等は何を意味problematic association with the state monad注)

+0

ハァッ、バナナのリンクはとても面白いです。コルーチンベースの解釈について、エドワード・クメットは第二のリンクで何を言いますか? –

+6

私のツールボックスには、「実装はあるが、表記がない」という意味の「魔法です」という言葉が使われています。 –

+0

私はカテゴリ理論でこれについて興味がありました。モナドの分類がリストモナド(Eugenia Chang)の代数の類型と同型である様子を見た後、私はモナドが数学的理論において非常に一般的で遍在的な構造をとらえているのか疑問に思った。それは、彼らがハスケルで使っている方法よりはるかに多いかもしれないと私に打ち明けます。私はまだ一緒にそれを一緒に置いていない。 – luqui

5

まず、モナドの役割について説明しましょう。モナドは非常に強力ですが、特定の意味では:モナドを使って何かを表現することができます。 Haskellは言語として、アクションループ、例外、突然変異、gotoなどを持っていません。モナドは言語内で表現することができます(特別なものではないので)。

これには肯定的な側面と否定的な側面があります。あなたが知っているすべての制御構造を、命令的プログラミングとそれ以外のものから表現することができます。私はちょうどあなたがちょっと変わった文脈で真ん中のどこかで計算に再び入ることを可能にするモナドを最近開発しました。そうすれば、計算を実行できます。失敗した場合は、わずかに調整した値でもう一度試してみてください。さらに、モナドのアクションはファーストクラスなので、ループや例外処理のようなものを構築する方法です。 whileはHaskellのC言語では基本的なものですが、実際にはちょうど普通の関数です。

否定的な側面は、モナドがあなたに何の保証もほとんど与えないということです。彼らはあなたが望むものを何でもでき、簡単に置くことができるほど強力です。言い換えれば、あなたが命令的言語から知っているのと同じように、ただそれを見てコードを推論するのは難しいかもしれません。

より一般的な抽象概念は、モナドとして表現することができないいくつかの概念を表現できるという意味で、より一般的です。しかしそれは物語の一部にすぎません。モナドの場合でも、というアプリケーションスタイルのスタイルを使用することができます。このスタイルでは、アプリケーションインターフェイスを使用して小さな独立した部分からプログラムを作成します。この利点は、コードを見るだけでコードについて考えることができ、残りのシステムに注意を払うことなくコンポーネントを開発できることです。

+2

さまざまなものがありますが、それらはFunctorまたはApplicativeで表現できますが、Monadでは表現できません。なぜHaskellのプログラマはMonadsにもっと魅了されたのですか? http://stackoverflow.com/a/7220548/2361979 – qznc

+0

IMHO、コミュニティは最初により一般的な問題に取り組む方法を学んだので、それの制限されたサブケースの使い方についてまだ学んでいます。最近、「Applicative」はこの前線で牽引力と終わりのあるものを得ていますが、まだ道があります。 –

+0

@qznc - 'Monad'インターフェースでは表現できない' Functor'や 'Applicative'インターフェースでは表現できないものはありません。 'Monad'は厳密に強力です。あなたのリンクはインターフェイスを実装することです。これはもう一つの方向です。もちろん、厳密にはそれほど強力でないインターフェースは、より多くの(またはこれに等しいが、この場合ではない)実現可能な実装を有する。 – Carl

12

まず、モナドが他のものよりもはるかにポピュラーであることは間違いないと思います。 FunctorとMonoidの両方には、モナドではないインスタンスが多数あります。しかし、どちらも非常に特殊です。 Functorはマッピング、Monoid連結を提供します。応募は、私が考えることができる1つのクラスです。それは、その言語に対する比較的最近の追加のために、かなりの力が与えられているとは思われません。

しかし、はい、モナドは非常に人気があります。その一部は表記法です。多くのMonoidは、実行中のアキュムレータ(本質的に暗黙のライター)に値を付加するだけのMonadインスタンスを提供します。 blaze-htmlライブラリは良い例です。理由は、私が思うに、型署名(>>=) :: Monad m => m a -> (a -> m b) -> m bの力です。 fmapとmappendは便利ですが、できることはかなり制限されています。しかし、バインドすると、さまざまなことを表現することができます。 IOモナドでは、もちろんストリームやFRPの前にIOに対する最高の純粋な機能的アプローチ(単純なタスクやコンポーネントを定義するのにも役立ちます)が可能です。しかし、それは暗黙の状態(Reader/Writer/ST)を提供するので、非常に面倒な変数の受け渡しを避けることができます。さまざまな状態のモナドは、ステートがシングルスレッドであることを保証し、融合前に純粋な(非IO)コードで可変構造を可能にするため、特に重要です。しかしbindには、ネストされたデータ構造(ListとSetのモナド)を平坦化するなど、いくつかのエキゾチックな使い方があります。それは表記の問題ではありません)。したがって、FunctorとMonoid(そしてFoldable、Alternative、Traversableなど)は、かなり単純な関数への標準化されたインターフェースを提供しますが、Monadのバインドはかなり柔軟です。

要するに、あなたのすべての理由にはいくつかの役割があると思います。モナドの人気は、歴史的事故(ドンノートとApplicativeの後期定義)と、権力と一般性(ファンクタ、モノイドなどとの関連)と理解力(矢印との関連)の組み合わせによるものです。

14

の場合タイプm :: * -> *Monadインスタンスを持ちますが、型がa -> m bのTuring-completeの構成が得られます。これは非常に便利なプロパティです。特定の意味から離れたさまざまなTuring-completeコントロールフローを抽象化することができます。これは、それをサポートするタイプを操作するための制御フローを抽象化することをサポートする最小のコンポジションパターンです。

これを例えば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 

は、構文(または生>>=オペレータ)を行うとともに、それらを組み合わせることでは、結合、無期限ループに名前を与え、ブール論理を完全なものにする。それはTuringの完全性を与えるのに十分なプリミティブのよく知られたセットです。単純な値ではなく、すべての関数がどのようにしてモナドの値に作用するのかに注目してください。すべてのモナディ効果は必要な場合にのみバインドされます。ifMの選択されたブランチからの効果のみが最終値にバインドされます。可能であれば、*&&*||は両方の引数を無視します。など。

ここで、これらの型シグネチャは、すべてのモナドのオペランドの関数を伴わなくてもかまいませんが、これは単なる認知の簡略化です。すべての非関数の引数と結果がに変更された場合、意味の違いはなく、底を無視します。そのコグニティブオーバーヘッドを最適化することは、ユーザーにとっては面白いことです。

ここでは、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は常に...両方の分岐が実行されますので、それは無限ループだという事実を除いて、大丈夫だろうと同じように、[OK]を、それはそうです。 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です。うん、これはどちらもうまくいかない。

実際にApplicativeインターフェイスで動作する私のMonadの例の唯一の機能はnotMです。その特定の関数は実際にはFunctorインターフェイスでうまく動作します。残り?彼らは失敗する。

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

チューリング完了組成は、正確にはです。Monadインターフェイスを興味深いものにします。チューリング完了コンポジションが許可されていない場合、プログラマーであるあなたには、あらかじめ適切にパッケージ化されていない特定のコントロールフローで、IOアクションを一緒に構成することは不可能です。 Monadプリミティブを使用して、の制御フローを表現し、IOタイプをHaskellのIO問題を管理するための実現可能な方法にすることができました。

IOよりも多くの種類が、意味的に有効なMonadインターフェイスを持っています。そして、Haskellには、インタフェース全体を抽象化するための言語機能があります。これらの要因により、Monadは可能な場合にインスタンスを提供する貴重なクラスです。そうすることで、具体的な型が何であるかにかかわらず、モナド型を扱うために用意されている既存のすべての抽象的な機能にアクセスできます。

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

+2

チューリング完全性とは何が関係していますか? (チューリング完了していない言語は完全にモナドを使用することができます(Agda参照) –

+0

@BenMillwood Agdaでは、チューリング完全な構造を一般的に使用することができます。非構造的再帰のように終了します。要点は、モナドインターフェイスでは、Turing-completeパターンを記述することができますが、使用する必要はありません。これは、 '= <<'が第1オペランドで使用する再帰構造体を第2オペランドの値に依存させるためです。次の最も近い合成演算子、 'Applicative'の' <*> 'は、再帰構造が第2オペランドの値に依存しないことを要求します。 – Carl

+2

「Applicative」と「Monad」の違いはチューリング完全性とは関係がないと私は考えています。コンセプトがどのように関連しているのか分かりません.Turing-completenessとは、おおよそ「チューリングマシンをシミュレートすることができ、1つでシミュレートできる」という意味です。当然のことながら、ここで議論されているすべてのものについて、関数a - > m bのモナディック構成がチューリングマシンをシミュレートできますか?どのようにそれをしようとするだろうか? –

0

モナドは、関数型言語で命令型プログラムを書くことができるdo表記のため特別です。モナドは抽象であり、より小さくて再利用可能なコンポーネント(命令型プログラムでもあります)から命令型プログラムをつなぎ合わせることができます。モナド変圧器は、新しい機能で不可欠な言語を強化するため、特殊です。

関連する問題