66 votes

Exemples d'utilisation du foncteur applicatif en Scala

Je sais que Monad peut être exprimé en Scala comme suit :

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

Je vois pourquoi c'est utile. Par exemple, étant donné deux fonctions :

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

Je peux facilement écrire la fonction getPhoneByUserId(userId: Int) depuis Option est une monade :

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

...

Maintenant je vois Applicative Functor en Scala :

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

Je me demande quand je devrais l'utiliser au lieu de monad . Je suppose que Option et List sont toutes deux Applicatives . Pouvez-vous donner des exemples simples d'utilisation de apply avec Option et Liste et expliquer pourquoi Je devrais l'utiliser au lieu de flatMap ?

78voto

Travis Brown Points 56342

A citation moi-même :

Alors pourquoi s'embêter avec des foncteurs applicatifs, alors que nous avons des monades ? Tout d'abord, il n'est tout simplement pas possible de fournir des instances de monades pour les éléments suivants certaines des abstractions avec lesquelles nous voulons travailler Validation est le parfait exemple.

Deuxièmement (et en relation avec cela), c'est juste une pratique de développement solide d'utiliser l'abstraction la moins puissante qui permettra d'accomplir le travail. En principe, En principe, cela permet des optimisations qui ne seraient pas possibles autrement. possible, mais surtout, cela rend le code que nous écrivons plus réutilisable. réutilisable.

Pour développer un peu plus le premier paragraphe : parfois vous n'avez pas le choix entre le code monadique et applicatif. Voir le reste du document cette réponse pour une discussion sur les raisons pour lesquelles vous pourriez vouloir utiliser la méthode de Scalaz. Validation (qui n'a pas et ne peut pas avoir d'instance de monade) pour modéliser les éléments suivants la validation.

En ce qui concerne l'optimisation, il faudra probablement attendre un certain temps avant que cela ne soit pertinent en Scala ou Scalaz, mais voyez par exemple la documentation de l'outil Haskell Data.Binary :

Le style applicatif permet parfois d'obtenir un code plus rapide, comme le montrent les exemples suivants binary va essayer d'optimiser le code en regroupant les lectures.

L'écriture de code applicatif vous permet d'éviter de faire des déclarations inutiles sur les dépendances entre les calculs - des déclarations auxquelles un code monadique similaire vous obligerait. Une bibliothèque ou un compilateur suffisamment intelligent pourrait en principe tirer parti de ce fait.

Pour rendre cette idée un peu plus concrète, considérons le code monadique suivant :

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

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

El for -La compréhension se réduit à quelque chose de plus ou moins similaire à ce qui suit :

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

Nous savons que maybeComputeN(whatever) ne dépend pas de s (en supposant qu'il s'agit de méthodes qui se comportent bien et qui ne modifient pas un état mutable en coulisse), mais le compilateur ne le sait pas - de son point de vue, il doit savoir que s avant de pouvoir commencer à calculer n .

La version applicative (utilisant Scalaz) ressemble à ceci :

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

Ici, nous déclarons explicitement qu'il n'y a pas de dépendance entre les deux calculs.

(Et oui, cette |@| La syntaxe est assez horrible - voir cet article de blog pour une discussion et des alternatives).

Le dernier point est vraiment le plus important, cependant. Choisir le moins Un outil puissant qui résoudra votre problème est un principe extrêmement puissant. Parfois, vous avez vraiment besoin d'une composition monadique dans votre getPhoneByUserId par exemple - mais souvent, ce n'est pas le cas.

Il est dommage que Haskell et Scala rendent actuellement le travail avec les monades beaucoup plus pratique (syntaxiquement, etc.) que le travail avec les foncteurs applicatifs, mais c'est surtout une question d'accident historique, et des développements tels que parenthèses idiomatiques sont un pas dans la bonne direction.

25voto

Yury Points 1303

Le foncteur sert à élever le calcul à la catégorie.

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

Et cela fonctionne parfaitement pour la fonction d'une variable.

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

Mais pour la fonction de 2 et plus après l'élévation à la catégorie nous avons la signature suivante :

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

Et c'est la signature du foncteur aplicatif. Et pour appliquer la valeur suivante à la fonction 'g' - le foncteur aplicatif est nécessaire.

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

Et après tout :

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

Le foncteur applicatif est un foncteur permettant d'appliquer une valeur spéciale (valeur dans la catégorie) à une fonction levée.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X