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.