72

Monad次のようにScalaで表現できることを私は知っています:

trait Monad[F[_]] {
  def flatMap[A, B](f: A => F[B]): F[A] => F[B]
}

便利な理由がわかります。たとえば、次の 2 つの関数があるとします。

getUserById(userId: Int): Option[User] = ...
getPhone(user: User): Option[Phone] = ...

はモナドなgetPhoneByUserId(userId: Int)ので、関数を簡単に書くことができます。Option

def getPhoneByUserId(userId: Int): Option[Phone] = 
  getUserById(userId).flatMap(user => getPhone(user))

...

今私はApplicative FunctorScalaで見る:

trait Applicative[F[_]] {
  def apply[A, B](f: F[A => B]): F[A] => F[B]
}

モナドの代わりにいつ使うべきなのだろうか。Option と List の両方がApplicatives. applyOption と List を使用する簡単な例を挙げて、代わりにそれを使用する理由を説明していただけますか? flatMap

4

2 に答える 2

81

自分自身を引用するには:

では、モナドがあるのに、なぜ Applicative Functor を気にする必要があるのでしょうか? まず第一に、処理したい抽象化のいくつかに対してモナドのインスタンスを提供することは単純に不可能です — <code>Validation は完璧な例です。

2 番目に (そして関連して)、仕事を成し遂げる最も強力でない抽象化を使用することは、堅実な開発プラクティスです。原則として、これにより、他の方法では不可能な最適化が可能になる可能性がありますが、さらに重要なことは、記述したコードがより再利用しやすくなるということです。

最初の段落を少し拡張すると、モナド コードとアプリケーション コードのどちらかを選択できない場合があります。検証をモデル化するために Scalaz (モナド インスタンスを持たない、または持たない)を使用する理由については、その回答の残りの部分を参照してください。Validation

最適化のポイントについて: これが Scala または Scalaz で一般的に関連するようになるまでには、おそらくしばらく時間がかかるでしょうが、たとえばHaskell のドキュメントをData.Binary参照してください。

Applicative スタイルはbinary 、読み取りをグループ化してコードを最適化しようとするため、コードが高速になる場合があります。

適用可能なコードを書くことで、計算間の依存関係について不必要な主張をすることを避けることができます — 同様のモナドコードがコミットするような主張。十分にスマートなライブラリまたはコンパイラ、原則としてこの事実を利用できます。

このアイデアをもう少し具体的にするために、次のモナド コードを考えてみましょう。

case class Foo(s: Symbol, n: Int)

val maybeFoo = for {
  s <- maybeComputeS(whatever)
  n <- maybeComputeN(whatever)
} yield Foo(s, n)

-comprehension は、for多かれ少なかれ次のようなものに脱糖します。

val maybeFoo = maybeComputeS(whatever).flatMap(
  s => maybeComputeN(whatever).map(n => Foo(s, n))
)

maybeComputeN(whatever)に依存しないことはわかっていますs(これらが、舞台裏で変更可能な状態を変更していない行儀の良いメソッドであると仮定すると) が、コンパイラは依存しません。その観点からは、s計算を開始する前に知る必要がありますn

適用可能なバージョン (Scalaz を使用) は次のようになります。

val maybeFoo = (maybeComputeS(whatever) |@| maybeComputeN(whatever))(Foo(_, _))

ここでは、2 つの計算の間に依存関係がないことを明示的に述べています。

(もちろん、この|@|構文はかなりひどいものです。いくつかの議論と代替案については、このブログ投稿を参照してください。)

ただし、最後のポイントは本当に最も重要です。問題を解決する最も強力でないツールを選択することは、非常に強力な原則ですgetPhoneByUserIdたとえば、メソッド内でモナド構成が本当に必要な場合もありますが、多くの場合、そうではありません。

Haskell と Scala の両方が現在、アプリカティブ ファンクターを使用するよりも (構文的になど) モナドを使用する方がはるかに便利であることは残念ですが、これはほとんど歴史的な偶然の問題であり、イディオム ブラケットのような開発は正しい方向への一歩です。方向。

于 2013-11-09T18:57:12.900 に答える
28

Functor は、計算をカテゴリに持ち上げるためのものです。

trait Functor[C[_]] {
  def map[A, B](f : A => B): C[A] => C[B]
}

そして、それは1つの変数の関数に対して完全に機能します。

val f = (x : Int) => x + 1

しかし、関数が 2 以上の場合、カテゴリに持ち上げると、次のシグネチャが得られます。

val g = (x: Int) => (y: Int) => x + y
Option(5) map g // Option[Int => Int]

そして、それはアプリカティブ ファンクターの署名です。そして、次の値を関数に適用するにはg— アプリケーション ファンクタが必要です。

trait Applicative[F[_]] {
  def apply[A, B](f: F[A => B]): F[A] => F[B]
} 

そして最後に:

(Applicative[Option] apply (Functor[Option] map g)(Option(5)))(Option(10))

Applicative functor は、リフトされた関数に特別な値 (カテゴリ内の値) を適用するためのファンクターです。

于 2013-11-09T20:05:07.070 に答える