web-dev-qa-db-ja.com

Applicativeスタイルの実用的な使い方は何ですか?

私はScalaプログラマーで、Haskellを今学んでいます。デコレータ、戦略パターンなどのOO概念の実際の使用例と、実際の例を見つけるのは簡単です。 。本やインターウェブはそれでいっぱいです。

これはどういうわけか機能的な概念には当てはまらないことに気づきました。適切な例:applicatives

私はApplicativeの実用的なユースケースを見つけるのに苦労しています。これまでに出会ったほとんどすべてのチュートリアルと本は、[]Maybeの例を提供しています。 FPコミュニティで注目を集めているので、Applicativeはそれよりも適切であると期待していました。

私はapplicativesの概念的基礎を理解していると思います(多分私は間違っています)、そして私は悟りの瞬間を長い間待っていました。しかし、それは起こっていないようです。プログラミングをしていると、「ユーレカ!ここでアプリが使える!」と喜んで叫ぶ瞬間はありませんでした。 (ここでも、[]Maybeを除く)。

誰かが、Applicativeを日常のプログラミングでどのように使用できるかを教えてもらえますか?パターンを見つけ始めるにはどうすればよいですか?ありがとう!

68
missingfaktor

警告:私の答えはかなり説教的/謝罪的です。だから私を訴えます。

さて、あなたの日々のHaskellプログラミングでどのくらいの頻度で新しいデータ型を作成しますか?独自のApplicativeインスタンスをいつ作成するかを知りたいようですが、正直なところ、独自のパーサーを作成しない限り、それほど多くのことを行う必要はありません。 singアプリケーションインスタンス、一方、頻繁に行うことを学ぶ必要があります。

Applicativeは、デコレータや戦略のような「デザインパターン」ではありません。これは抽象化であり、はるかに普及し、一般的に有用ですが、具体的ではありません。 「実用的な用途」を見つけるのに苦労している理由は、その使用例がほとんど単純すぎるためです。デコレータを使用して、ウィンドウにスクロールバーを配置します。戦略を使用して、チェスボットの攻撃的な動きと防御的な動きの両方のインターフェイスを統合します。しかし、Applicativeは何のためにありますか?まあ、それらはもっと一般化されているので、それらが何のためにあるのかを言うのは難しいです、そしてそれは大丈夫です。 Applicativeはパーサーコンビネータとして便利です。 Yesod Webフレームワークは、Applicativeを使用して、フォームの設定と情報の抽出を支援します。見てみると、Applicativeには100万と1つの用途があります。それはいたるところにあります。しかし、それはとても抽象的なので、あなたはそれがあなたの人生を楽にするのを助けることができる多くの場所を認識するためにそれの感触をつかむ必要があります。

9
Dan Burton

Applicativeは、いくつかの変数の単純な古い関数があり、引数はあるが、ある種のコンテキストにまとめられている場合に最適です。たとえば、昔ながらの連結関数(++)がありますが、I/Oを介して取得された2つの文字列に適用したいとします。次に、IOが適用可能なファンクターであるという事実が助けになります。

Prelude Control.Applicative> (++) <$> getLine <*> getLine
hi
there
"hithere"

Maybe以外の例を明示的に要求したとしても、それは私には優れたユースケースのように思われるので、例を示します。いくつかの変数の通常の関数がありますが、必要なすべての値があるかどうかはわかりません(それらのいくつかは計算に失敗し、Nothingを生成する可能性があります)。つまり、基本的に「部分値」があるため、関数を部分関数に変換する必要があります。部分関数は、入力のいずれかが未定義の場合は未定義です。次に

Prelude Control.Applicative> (+) <$> Just 3 <*> Just 5
Just 8

だが

Prelude Control.Applicative> (+) <$> Just 3 <*> Nothing
Nothing

それはまさにあなたが望むものです。

基本的な考え方は、通常の関数を、必要な数の引数に適用できるコンテキストに「持ち上げる」ということです。基本的なApplicativeに対するFunctorの追加の能力は、任意のアリティの関数を持ち上げることができるのに対し、fmapは単項関数しか持ち上げることができないことです。

65
Tom Crockett

多くのApplicativeもモナドであるため、この質問には本当に2つの側面があると思います。

両方が利用可能なのに、なぜモナドインターフェイスの代わりにアプリケーションインターフェイスを使用したいのですか?

これは主にスタイルの問題です。モナドにはdo-表記の構文糖衣がありますが、適用可能なスタイルを使用すると、コードがよりコンパクトになることがよくあります。

この例では、タイプFooがあり、このタイプのランダムな値を作成します。 IOのモナドインスタンスを使用して、次のように記述します。

data Foo = Foo Int Double

randomFoo = do
    x <- randomIO
    y <- randomIO
    return $ Foo x y

適用可能なバリアントはかなり短いです。

randomFoo = Foo <$> randomIO <*> randomIO

もちろん、liftM2を使用して同様の簡潔さを得ることができますが、適用可能なスタイルは、アリティ固有のリフティング関数に依存する必要があるよりも優れています。

実際には、ポイントフリースタイルを使用するのとほとんど同じ方法でApplicativeを使用しています。操作が他の操作の構成としてより明確に表現されている場合に、中間値に名前を付けないようにするためです。

モナドではないアプリケーションを使用したいのはなぜですか?

Applicativeはモナドよりも制限されているため、これは、Applicativeに関するより有用な静的情報を抽出できることを意味します。

この例は、Applicativeパーサーです。モナドパーサーは(>>=) :: Monad m => m a -> (a -> m b) -> m bを使用した順次構成をサポートしますが、アプリケーションパーサーは(<*>) :: Applicative f => f (a -> b) -> f a -> f bのみを使用します。タイプによって違いが明らかになります。モナドパーサーでは、入力に応じて文法が変わる可能性がありますが、アプリケーションパーサーでは、文法が固定されています。

このようにインターフェイスを制限することで、たとえば、パーサーが空の文字列を実行せずに受け入れるかどうかを決定できます。また、最適化に使用できる最初のセットと次のセットを決定したり、最近遊んでいるように、より優れたエラー回復をサポートするパーサーを構築したりすることもできます。

48
hammar

Functor、Applicative、Monadをデザインパターンと考えています。

Future [T]クラスを作成したいとします。つまり、計算される値を保持するクラスです。

Javaの考え方では、次のように作成できます

trait Future[T] {
  def get: T
}

'get'は、値が使用可能になるまでブロックします。

あなたはこれに気づき、コールバックを取るように書き直すかもしれません:

trait Future[T] {
  def foreach(f: T => Unit): Unit
}

しかし、将来に2つの用途があるとしたら、どうなるでしょうか。これは、コールバックのリストを保持する必要があることを意味します。また、メソッドがFuture [Int]を受け取り、内部のIntに基づいて計算を返す必要がある場合はどうなりますか?または、2つの先物があり、それらが提供する値に基づいて何かを計算する必要がある場合はどうしますか?

ただし、FPの概念を知っている場合は、Tを直接操作する代わりに、Futureインスタンスを操作できることを知っています。

trait Future[T] {
  def map[U](f: T => U): Future[U]
}

これで、アプリケーションが変更され、含まれている値を処理する必要があるたびに、新しいFutureが返されるようになりました。

この道を歩み始めたら、そこで止まることはできません。 2つの先物を操作するには、アプリケーションとしてモデル化する必要があります。先物を作成するには、先物のモナド定義が必要です。

更新:@Ericが提案したように、私はブログ投稿を書きました: http://www.tikalk.com/incubator/blog/functional-programming-scala-rest-us

16
IttayD

私はついに、Applicativeがそのプレゼンテーションで日常のプログラミングにどのように役立つかを理解しました。

http://applicative-errors-scala.googlecode.com/svn/artifacts/0.6/chunk-html/index.html

Autorは、Applicativeが検証の組み合わせと失敗の処理にどのように役立つかを示しています。

プレゼンテーションはScalaで行われますが、作成者はHaskellの完全なコード例JavaおよびC#も提供しています。

13
paradigmatic

Applicativeは、モナドコードの一般的な使用法を容易にすると思います。関数を適用したいが、関数がモナドではなく、適用したい値がモナドであるという状況が何回ありましたか?私にとって:かなりの回数!
これは私が昨日書いた例です:

ghci> import Data.Time.Clock
ghci> import Data.Time.Calendar
ghci> getCurrentTime >>= return . toGregorian . utctDay

applicativeを使用したこれと比較して:

ghci> import Control.Applicative
ghci> toGregorian . utctDay <$> getCurrentTime

このフォームは「より自然」に見えます(少なくとも私の目には:)

9
oliver

「Functor」からApplicativeに来ると、「fmap」を一般化して、いくつかの引数(liftA2)または一連の引数(<*>を使用)に作用することを簡単に表現します。

「モナド」からApplicativeに来ると、計算が計算される値に依存することはありません。具体的には、パターンマッチングを行って戻り値を分岐することはできません。通常、できることは、それを別のコンストラクターまたは関数に渡すことだけです。

したがって、ApplicativeはFunctorとMonadの間に挟まれていると思います。モナド計算からの値で分岐していないことを認識することは、いつ適用に切り替えるかを確認する1つの方法です。

5
Chris Kuklewicz

Aesonパッケージから抜粋した例を次に示します。

data Coord = Coord { x :: Double, y :: Double }

instance FromJSON Coord where
   parseJSON (Object v) = 
      Coord <$>
        v .: "x" <*>
        v .: "y"
4
qubital

ZipListのように、適用可能なインスタンスを持つことができるが、モナドインスタンスを持つことができないADTがいくつかあります。これは、Applicativeとモナドの違いを理解するときに非常に役立つ例でした。非常に多くのApplicativeもモナドであるため、ZipListのような具体的な例がなければ、2つの違いを簡単に確認できません。

4
Sukant Hajra

Hackageでパッケージのソースを閲覧し、既存のHaskellコードで適用可能なファンクターなどがどのように使用されているかを直接確認することは価値があると思います。

2

以下に引用するディスカッションで、アプリケーションファンクターの実際の使用例を説明しました。

コード例は、サブタイプの概念的な形式で型クラスを非表示にする私の架空の言語の擬似コードであることに注意してください。したがって、applyのメソッド呼び出しが表示された場合は、型クラスモデルに変換するだけです。 ScalazまたはHaskellの_<*>_。

配列またはハッシュマップの要素をnullまたはnoneでマークして、インデックスまたはキーが有効であるが値がないことを示す場合、Applicativeは、値を持つ要素に操作を適用するときに、ボイラープレートが値のない要素をスキップすることなく有効になります。さらに重要なことに、アプリオリに不明なWrappedセマンティクス、つまり_Hashmap[Wrapped[T]]_を超えるTの操作(アプリケーションは構成可能であるがモナドは構成可能ではないため、_Hashmap[Wrapped[Wrapped2[T]]]_などの任意のレベルの構成)を自動的に処理できます。

コードがどのように理解しやすくなるかはすでに想像できます。私はそこにたどり着くためのすべての雑用ではなく、セマンティクスに焦点を当てることができます。私のセマンティクスはWrappedの拡張の下で開かれますが、すべてのサンプルコードはそうではありません。

重要なことに、前の例ではApplicativeの戻り値がエミュレートされていないことを指摘するのを忘れました。これは、ListNullable、またはOptionではなくMaybeになります。したがって、例を修復しようとしても、_Applicative.apply_をエミュレートしていませんでした。

functionToApplyは_Applicative.apply_への入力であるため、コンテナーが制御を維持することに注意してください。

list1.apply( list2.apply( ... listN.apply( List.lift(functionToApply) ) ... ) )

同等に。

list1.apply( list2.apply( ... listN.map(functionToApply) ... ) )

そして、コンパイラが上記に変換する私の提案した構文糖衣。

funcToApply(list1, list2, ... list N)

ここですべてをコピーすることはできないので、 そのインタラクティブなディスカッション を読むと便利です。そのブログの所有者が誰であるかを考えると、URLが壊れないことを期待しています。たとえば、私は議論のさらに下から引用します。

ステートメント外の制御フローと割り当ての混同は、ほとんどのプログラマーにとっておそらく望ましくありません。

Applicative.applyは、型パラメーターのネスト(構成)の任意のレベルで、パラメーター化された型(別名ジェネリック)への関数の部分適用を一般化するためのものです。これはすべて、より一般化された構成を可能にすることです。タマネギを裏返しに剥がすことができないのと同様に、関数の完了した評価(つまり戻り値)の外にそれを引っ張ることによって一般性を達成することはできません。

したがって、それは混乱ではなく、現在利用できない新しい自由度です。ディスカッションアップスレッドによると、言語にはこのような自由度がないため、例外をスローするか、グローバル変数に格納する必要があるのはこのためです。そして、それはこれらの圏論ファンクターの唯一のアプリケーションではありません(モデレーターキューの私のコメントで説明されています)。

現在モデレーターキューでスタックしているScala、F#、およびC#で検証を抽象化するためのリンクを提供しました。不快なC#バージョンのコードを比較してください。その理由は、C#が一般化されていないためです。プログラムが大きくなるにつれて、C#のケース固有のボイラープレートが幾何学的に爆発することを直感的に期待しています。

1