web-dev-qa-db-ja.com

パーセク:Applicatives vs Monads

私はParsec(Haskellの経験がほとんどない)から始めたばかりで、モナドやApplicativeの使用について少し混乱しています。 「RealWorldHaskell」、「Write You a Haskell」を読んだ後の全体的な感触と、ここでの質問は、Applicativeが好まれるということですが、実際にはわかりません。

だから私の質問は:

  • どのアプローチが好ましいですか?
  • モナドとApplicativeを混在させることはできますか(他よりも便利な場合に使用してください)
  • 最後の答えが「はい」の場合、私はそれをすべきですか?
21
José Miguel

一般的に、あなたにとって最も意味のあるものから始めてください。その後、次のことを考慮してください。

可能であれば、Applicative(またはFunctor)を使用することをお勧めします。一般に、GHCのようなコンパイラは、Monadよりも単純である可能性があるため、これらのインスタンスを最適化する方が簡単です。一般的なコミュニティアドバイスの投稿- [〜#〜] amp [〜#〜] は、制約をできるだけ一般的にすることだと思います。 GHC拡張機能の使用をお勧めします ApplicativeDodo表記を一律に使用でき、それだけでApplicative制約を取得できるためです。が必要です。

ParsecTパーサータイプはApplicativeMonadの両方のインスタンスであるため、2つを組み合わせて組み合わせることができます。これを行う方が読みやすい状況があります-これはすべて状況によって異なります。

また、 megaparsec の使用を検討してください。 megaparsecは、より積極的に保守されている、parsecのより最近のフォークです。

編集

私の答えとコメントを読み直して、私は本当に明確にするのに良い仕事をしなかったという2つのこと:

  • Applicativeを使用する主な利点は、多くのタイプで、はるかに効率的な実装が可能になることです(たとえば、(<*>)ap よりもパフォーマンスが高くなります)。

  • (+) <$> parseNumber <*> parseNumberのようなものを書きたいだけなら、ApplicativeDoにドロップする必要はありません-もっと冗長になります。 ApplicativeDoを使用するのは、非常に長い、またはネストされた適用式を記述し始めたときだけです。

12
Alec

ApplicativeMonadの主な意味の違いに注意して、それぞれが適切な場合を判断することをお勧めします。タイプの比較:

(<*>) :: m (s -> t) -> m s -> m t
(>>=) :: m s -> (s -> m t) -> m t

<*>をデプロイするには、関数と引数の2つの計算を選択し、それらの値をアプリケーションごとに結合します。 >>=をデプロイするには、1つの計算を選択し、その結果の値を使用して次の計算を選択する方法を説明します。これは、「バッチモード」と「インタラクティブ」操作の違いです。

構文解析に関しては、Applicative(失敗とAlternativeを与える選択で拡張)は、文法の文脈自由側面をキャプチャします。入力の別の部分に使用する文法を決定するために、入力の一部から解析ツリーを検査する必要がある場合にのみ、Monadが提供する追加の機能が必要になります。たとえば、フォーマット記述子を読み取ってから、そのフォーマットの入力を読み取る場合があります。モナドの余分な力の使用を最小限に抑えることで、どの値依存性が不可欠であるかがわかります。

構文解析から並列処理に移行すると、>>=を本質的な価値依存性にのみ使用するというこのアイデアにより、負荷を分散する機会について明確になります。 2つの計算が<*>と組み合わされた場合、どちらももう一方を待つ必要はありません。 Applicative-when-you-can-but-monadic-when-you-mustは、速度の公式です。 ApplicativeDoのポイントは、モナドスタイルで記述されているために誤ってオーバーシーケンシャルになっているコードの依存関係分析を自動化することです。

あなたの質問はコーディングスタイルにも関係しており、意見は自由に異なります。しかし、話をさせてください。私はStandardMLからHaskellに来ました。そこでは、例外のスローや参照の変更などのいたずらなことをしたとしても、直接的なスタイルでプログラムを書くことに慣れていました。 MLで何をしていましたか?超高純度型理論の実装に取り​​組んでいます(法的な理由で名前が付けられていない場合があります)。 inその型理論で作業しているとき、例外を使用する直接スタイルのプログラムを作成することはできませんでしたが、可能な限り直接スタイルに近づける方法として、適用可能なコンビネータを作成しました。

Haskellに引っ越したとき、疑似命令型のプログラミングは、ほんのわずかな意味の不純さに対する罰であると人々が考えているように思われる程度を発見するのは恐ろしいことでした(もちろん、非終了は別として)。私は、意味の違い、つまりモナドインターフェイスの有用な弱体化を表すことを理解するずっと前に、スタイルの選択として適用可能なコンビネータを採用しました(そして、「イディオムブラケット」を使用した直接スタイルにさらに近づきました)。私は、do-notationが式の構造の断片化と物事の不必要な命名を必要とする方法が好きではありませんでした(そして今でも好きではありません)。

つまり、関数型コードを命令型コードよりもコンパクトで読みやすくするのと同じことで、アプリケーションスタイルもdo表記よりもコンパクトで読みやすくなります。 ApplicativeDoは、リファクタリングする時間がないモナドスタイルで作成されたプログラムをより適用可能にする(場合によってはより高速)ための優れた方法であることを感謝します。しかし、そうでなければ、何が起こっているのかを知るためのより良い方法は、適用的であるが、モナド的である必要があることでもあると私は主張します。

65
pigworker

@pigworkerからのフォロー(私はここで残念ながらコメントするにはあまりにも新しいです)それは注目に値しますjoin $ fM <*> ... <*> ... <*> ...パターンとしても。同じ方法で「bind1、bind2、bind3 ...」ファミリをネットします<$>および<*>「fmap1、fmap2、fmap3」のものを入手してください。

様式的には、コンビネータに十分慣れている場合は、doを使用するのとほぼ同じようにletを使用できます。これは、何かに名前を付けたいときに強調表示する方法です。たとえば、パーサーでより頻繁に名前を付けたいと思う傾向があります。これは、おそらく仕様の名前に対応しているためです。

6