76 votes

Quelles sont les utilisations pratiques du style applicatif ?

Je suis un programmeur Scala, qui apprend maintenant Haskell. Il est facile de trouver des cas d'utilisation pratiques et des exemples concrets pour les concepts OO, tels que les décorateurs, le modèle de stratégie, etc. Les livres et les sites web en sont remplis.

Je me suis rendu compte que ce n'est pas toujours le cas pour les concepts fonctionnels. Un exemple concret : applicatifs .

J'ai du mal à trouver des cas pratiques d'utilisation des applicatifs. Presque tous les tutoriels et les livres que j'ai rencontrés jusqu'à présent fournissent les exemples suivants [] et Maybe . Je m'attendais à ce que les applicatifs soient plus applicables que cela, vu toute l'attention qu'ils reçoivent dans la communauté FP.

Je pense que je comprends la base conceptuelle de applicatifs (peut-être que je me trompe), et j'ai longtemps attendu mon moment d'illumination. Mais il ne semble pas se produire. Jamais, en programmant, je n'ai eu un moment où je criais avec joie "Eureka ! Je peux utiliser l'applicatif ici !" (sauf, encore une fois, pour [] et Maybe ).

Quelqu'un peut-il m'indiquer comment utiliser les applicatifs dans une programmation quotidienne ? Comment puis-je commencer à repérer le modèle ? Merci !

1 votes

C'est la première fois que ces deux articles m'ont donné envie d'apprendre ce genre de choses : debasishg.blogspot.com/2010/11/explorer-scalaz.html debasishg.blogspot.com/2011/02/

0 votes

Étroitement liées : stackoverflow.com/questions/2120509/

77voto

pelotom Points 14817

Les applications sont excellentes lorsque vous avez une vieille fonction simple de plusieurs variables, et que vous avez les arguments mais qu'ils sont enveloppés dans une sorte de contexte. Par exemple, vous avez la bonne vieille fonction concaténer (++) mais vous voulez l'appliquer à 2 cordes qui ont été acquises par le biais des E/S. Alors le fait que IO est un foncteur applicatif vient à la rescousse :

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

Même si vous avez explicitement demandé de ne pas Maybe Cela me semble être un excellent cas d'utilisation, alors je vais donner un exemple. Vous avez une fonction régulière de plusieurs variables, mais vous ne savez pas si vous avez toutes les valeurs dont vous avez besoin (certaines d'entre elles peuvent avoir échoué à calculer, ce qui donne Nothing ). Donc, essentiellement parce que vous avez des "valeurs partielles", vous voulez transformer votre fonction en une fonction partielle, qui est indéfinie si l'une de ses entrées est indéfinie. Alors

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

mais

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

ce qui est exactement ce que vous voulez.

L'idée de base est de "lever" une fonction ordinaire dans un contexte où elle peut être appliquée à autant d'arguments que vous le souhaitez. La puissance supplémentaire de Applicative sur un simple Functor est qu'il peut lever des fonctions d'arité arbitraire, alors que fmap ne peut lever qu'une fonction unaire.

2 votes

Je ne suis pas sûr que l'exemple de l'IO applicatif soit bon, car l'applicatif ne se préoccupe pas tellement de l'ordre, mais en (| (++) getLine getLine |) l'ordre des deux getLine les actions deviennent significatives pour le résultat...

2 votes

@hvr : Dans quel ordre (<*>) L'ordre dans lequel les choses sont classées est arbitraire, mais il est généralement de gauche à droite par convention, de sorte que f <$> x <*> y == do { x' <- x; y' <- y; return (f x y) }

0 votes

@C.A.McCann, ...alors est-ce que c'est considéré comme une bonne pratique de codage d'un point de vue stylistique d'exprimer quelque chose en style applicatif qui dépend en fait de la séquence correcte des effets (qui, à mon avis, serait mieux traitée en utilisant le style monadique) ?

55voto

hammar Points 89293

Comme de nombreux applicatifs sont également des monades, j'ai l'impression qu'il y a vraiment deux côtés à cette question.

Pourquoi voudrais-je utiliser l'interface applicative au lieu de l'interface monadique lorsque les deux sont disponibles ?

C'est surtout une question de style. Bien que les monades aient le sucre syntaxique de do -L'utilisation du style applicatif conduit souvent à un code plus compact.

Dans cet exemple, nous avons un type Foo et nous voulons construire des valeurs aléatoires de ce type. En utilisant l'instance de monade pour IO nous pourrions écrire

data Foo = Foo Int Double

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

La variante applicative est un peu plus courte.

randomFoo = Foo <$> randomIO <*> randomIO

Bien sûr, nous pourrions utiliser liftM2 pour obtenir une brièveté similaire, cependant le style applicatif est plus propre que de devoir s'appuyer sur des fonctions de levage spécifiques à l'arité.

En pratique, je me retrouve le plus souvent à utiliser les applicatifs de la même manière que le style sans points : Pour éviter de nommer des valeurs intermédiaires lorsqu'une opération est plus clairement exprimée comme une composition d'autres opérations.

Pourquoi voudrais-je utiliser un applicatif qui n'est pas une monade ?

Les applicatifs étant plus restreints que les monades, cela signifie que vous pouvez extraire des informations statiques plus utiles à leur sujet.

Les analyseurs applicatifs en sont un exemple. Alors que les analyseurs monadiques prennent en charge la composition séquentielle à l'aide de la fonction (>>=) :: Monad m => m a -> (a -> m b) -> m b les analyseurs applicatifs utilisent uniquement (<*>) :: Applicative f => f (a -> b) -> f a -> f b . Les types rendent la différence évidente : dans les analyseurs monadiques, la grammaire peut changer en fonction de l'entrée, alors que dans un analyseur applicatif, la grammaire est fixe.

En limitant l'interface de cette manière, nous pouvons par exemple déterminer si un analyseur syntaxique acceptera la chaîne vide sans l'exécuter . Nous pouvons également déterminer le premier ensemble et l'ensemble suivant, ce qui peut être utilisé pour l'optimisation, ou, comme je l'ai fait récemment, pour construire des analyseurs syntaxiques qui supportent une meilleure récupération des erreurs.

4 votes

Iinm, les compréhensions de monades récemment ajoutées dans ghc donnent presque le même niveau de compacité que les combinateurs applicatifs : [Foo x y | x <- randomIO, y <- randomIO]

3 votes

@Dan : c'est certainement plus court que l'exemple 'do', mais ce n'est toujours pas sans point, ce qui semble être souhaitable dans le monde Haskell.

17voto

IttayD Points 10490

Je pense que Functor, Applicative et Monad sont des modèles de conception.

Imaginez que vous voulez écrire une classe Future[T]. C'est-à-dire une classe qui contient des valeurs qui doivent être calculées.

Dans un esprit Java, vous pourriez le créer de la manière suivante

trait Future[T] {
  def get: T
}

Où "obtenir" bloque jusqu'à ce que la valeur soit disponible.

Vous pourriez vous en rendre compte, et le réécrire pour qu'il prenne un callback :

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

Mais alors que se passe-t-il s'il y a deux utilisations pour l'avenir ? Cela signifie que vous devez conserver une liste de callbacks. De même, que se passe-t-il si une méthode reçoit un Future[Int] et doit retourner un calcul basé sur l'Int qu'il contient ? Ou que faites-vous si vous avez deux futures et que vous devez calculer quelque chose sur la base des valeurs qu'ils fourniront ?

Mais si vous connaissez les concepts FP, vous savez qu'au lieu de travailler directement sur T, vous pouvez manipuler l'instance Future.

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

Maintenant, votre application change de façon à ce que chaque fois que vous devez travailler sur la valeur contenue, vous renvoyez simplement un nouveau Future.

Une fois que vous vous êtes engagé dans cette voie, vous ne pouvez pas vous arrêter là. Vous vous rendez compte que pour manipuler deux futurs, il suffit de modéliser en tant qu'applicatif, pour créer des futurs, il faut une définition de monade pour le futur, etc.

MISE À JOUR : Comme suggéré par @Eric, j'ai écrit un billet de blog : http://www.tikalk.com/incubator/blog/functional-programming-scala-rest-us

1 votes

C'est une façon intéressante d'introduire les foncteurs, les applicatifs et les monades, qui mériterait un article de blog complet montrant les détails derrière "etc...".

0 votes

Le lien semble cassé à partir d'aujourd'hui. Le lien de la machine à remonter le temps est web.archive.org/web/20140604075710/http://www.tikalk.com/

14voto

paradigmatic Points 20871

Grâce à cette présentation, j'ai enfin compris comment les applicatifs peuvent aider la programmation au quotidien :

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

L'auteur montre comment les applicatifs peuvent aider à combiner les validations et à gérer les échecs.

La présentation est en Scala, mais l'auteur fournit également l'exemple de code complet pour Haskell, Java et C#.

2 votes

Le lien est malheureusement rompu.

1 votes

Lien vers la machine à remonter le temps : web.archive.org/web/20100818221025/http://…

10voto

oliver Points 2296

Je pense que les applicatifs facilitent l'utilisation générale du code monadique. Combien de fois vous êtes-vous retrouvé dans la situation où vous vouliez appliquer une fonction mais que cette fonction n'était pas monadique et que la valeur à laquelle vous vouliez l'appliquer était monadique ? Pour moi : un grand nombre de fois !
Voici un exemple que j'ai écrit hier :

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

par rapport à celui-ci en utilisant l'Applicatif :

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

Cette forme semble plus "naturelle" (du moins à mes yeux :)

2 votes

En fait, <$> est juste fmap, c'est réexporté de Data.Functor.

1 votes

@Sjoerd Visscher : correct... L'utilisation de <$> est toujours plus attrayante puisque fmap n'est pas un opérateur infixe par défaut. Il faudrait donc que ce soit plutôt comme ça : fmap (toGregorian . utctDay) getCurrentTime

1 votes

Le problème avec fmap est qu'elle ne fonctionne pas lorsque vous souhaitez appliquer une fonction simple à arguments multiples à des valeurs monadiques multiples ; c'est là que la solution est la suivante Applicative L'adéquat entre en jeu.

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