72

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

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

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

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

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

4

11 に答える 11

74

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

于 2011-08-18T08:03:16.807 に答える
54

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

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

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

この例では、タイプが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に関するより有用な静的情報を抽出できることを意味します。

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

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

于 2011-08-18T13:52:17.440 に答える
16

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

于 2011-08-21T10:54:23.420 に答える
14

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

https://web.archive.org/web/20100818221025/http://applicative-errors-scala.googlecode.com/svn/artifacts/0.6/chunk-html/index.html

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

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

于 2011-08-18T07:55:44.407 に答える
11

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

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

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

于 2011-08-19T17:35:12.670 に答える
9

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

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

于 2011-08-18T08:30:40.333 に答える
7

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

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

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

于 2011-08-18T13:57:32.030 に答える
5

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

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

instance FromJSON Coord where
   parseJSON (Object v) = 
      Coord <$>
        v .: "x" <*>
        v .: "y"
于 2011-08-18T08:53:22.427 に答える
4

ZipListのように、アプリケーションインスタンスを持つことができるが、モナディックインスタンスを持つことができないADTがいくつかあります。これは、Applicativeとモナドの違いを理解するときに非常に役立つ例でした。非常に多くのApplicativeもモナドであるため、ZipListのような具体的な例がなければ、2つの違いを簡単に理解できません。

于 2012-02-09T23:29:14.363 に答える
2

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

于 2011-08-19T04:12:01.713 に答える
1

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

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

配列またはハッシュマップの要素に、またはそれらのインデックスまたはキーが有効であるが価値がないことを示すマークを付けるとnullnoneApplicative を持つ要素に操作を適用するときに、ボイラープレートなしで値のない要素をスキップできます。Wrappedさらに重要なことに、アプリオリに未知のセマンティクス、つまりTオーバーで の操作を自動的に処理できます(たとえば、アプリケーションは構成可能であるがモナドは構成可能ではないためHashmap[Wrapped[T]]、任意のレベルの構成で)。Hashmap[Wrapped[Wrapped2[T]]]

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

重要なことに、前の例では、の戻り値がエミュレートされていないことを指摘するのを忘れていました。これは、、、、またはではなく、にApplicativeなります 。したがって、あなたの例を修復しようとしても、エミュレートしていませんでした。ListNullableOptionMaybeApplicative.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#のケース固有のボイラープレートが幾何学的に爆発することを直感的に期待しています。

于 2013-03-14T07:51:41.877 に答える