Je suis nouveau chez Haskell et je lis sur les foncteurs et les foncteurs applicatifs. Ok, je comprends les foncteurs et comment je peux les utiliser, mais je ne comprends pas pourquoi applicatif les foncteurs sont utiles et comment je peux les utiliser dans Haskell. Pouvez-vous m'expliquer avec un exemple simple pourquoi j'ai besoin de foncteurs applicatifs?
Les foncteurs applicatifs sont une construction qui fournit le milieu entre les foncteurs et monades , et sont donc plus répandus que les monades, bien que plus utiles que les foncteurs. Normalement, vous pouvez simplement mapper une fonction sur un foncteur. Les foncteurs applicatifs vous permettent de prendre une fonction "normale" (en prenant des arguments non fonctoriels) de l'utiliser pour opérer sur plusieurs valeurs qui sont dans des contextes foncteurs. En corollaire, cela vous donne une programmation efficace sans monades.
Une belle explication autonome et pleine d'exemples peut être trouvée ici . Vous pouvez également lire n exemple d'analyse pratique développé par Bryan O'Sullivan, qui ne nécessite aucune connaissance préalable.
Les foncteurs applicatifs sont utiles lorsque vous avez besoin de séquencer des actions, mais n'avez pas besoin de nommer de résultats intermédiaires. Ils sont donc plus faibles que les monades, mais plus forts que les foncteurs (ils n'ont pas d'opérateur de liaison explicite, mais ils permettent d'exécuter des fonctions arbitraires à l'intérieur du foncteur).
Quand sont-ils utiles? Un exemple courant est l'analyse, où vous devez exécuter un certain nombre d'actions qui lisent les parties d'une structure de données dans l'ordre, puis collent tous les résultats ensemble. C'est comme une forme générale de composition de fonction:
f a b c d
où vous pouvez penser à a
, b
et ainsi de suite comme les actions arbitraires à exécuter, et f
comme le foncteur à appliquer au résultat.
f <$> a <*> b <*> c <*> d
J'aime à les considérer comme des "espaces blancs" surchargés. Ou, que les fonctions Haskell régulières se trouvent dans le foncteur applicatif d'identité.
Conor McBride et Ross Paterson Functional Pearl sur le style a plusieurs bons exemples. Il est également chargé de populariser le style en premier lieu. Ils utilisent le terme "idiome" pour "foncteur applicatif", mais à part cela, c'est assez compréhensible.
Il est difficile de trouver des exemples où vous avez besoin de foncteurs applicatifs. Je peux comprendre pourquoi un programmeur Haskell intermédiaire se poserait cette question puisque la plupart des textes d'introduction présentent des instances dérivées de Monads utilisant des foncteurs applicatifs uniquement comme interface pratique.
L'idée clé, comme mentionné ici et dans la plupart des introductions au sujet, est que les foncteurs applicatifs sont entre les foncteurs et les monades (même entre les foncteurs et les flèches). Toutes les monades sont des foncteurs applicatifs, mais tous les foncteurs ne sont pas applicatifs.
Donc nécessairement, parfois, nous pouvons utiliser des combinateurs applicatifs pour quelque chose que nous ne pouvons pas utiliser pour des combinateurs monadiques. Une de ces choses est ZipList
(voir aussi cette question SO pour certains détails ), qui est juste un wrapper autour afin d'avoir une instance Applicative différente que celle dérivée de l'instance Monad de list. La documentation Applicative utilise la ligne suivante pour donner une notion intuitive à quoi sert ZipList
:
f <$> ZipList xs1 <*> ... <*> ZipList xsn = ZipList (zipWithn f xs1 ... xsn)
Comme indiqué ici , il est possible de créer des instances Monad originales qui fonctionnent presque pour ZipList.
Il existe d'autres foncteurs applicatifs qui ne sont pas des monades (voir this SO question) et ils sont faciles à trouver. Avoir une interface alternative pour les monades est agréable et tout , mais parfois faire une Monade est inefficace, compliqué, voire impossible, et c'est alors que vous avez besoin Foncteurs applicatifs.
clause de non-responsabilité: la création de foncteurs applicatifs peut également être inefficace, compliquée et impossible, en cas de doute, consultez votre théoricien de catégorie local pour une utilisation correcte des foncteurs applicatifs.
D'après mon expérience, les foncteurs applicatifs sont parfaits pour les raisons suivantes:
Certains types de structures de données admettent des types de compositions puissants, mais ne peuvent pas vraiment être transformés en monades. En fait, la plupart des abstractions de la programmation réactive fonctionnelle entrent dans cette catégorie. Alors que nous pourrions techniquement être en mesure de faire par exemple Behavior
(alias Signal
) une monade, cela ne peut généralement pas être fait efficacement. Les foncteurs applicatifs nous permettent d'avoir toujours des compositions puissantes sans sacrifier l'efficacité (certes, il est parfois un peu plus délicat d'utiliser un applicatif qu'une monade, juste parce que vous n'avez pas autant de structure pour travailler).
Le manque de dépendance des données dans un foncteur applicatif vous permet par exemple de parcourir une action à la recherche de tous les effets qu'elle pourrait produire sans disposer des données. Vous pourriez donc imaginer un "formulaire web" applicatif, utilisé comme ceci:
userData = User <$> field "Name" <*> field "Address"
et vous pourriez écrire un moteur qui traverserait pour trouver tous les champs utilisés et les afficher dans un formulaire, puis lorsque vous récupérez les données, exécutez-le à nouveau pour obtenir le User
construit. Cela ne peut pas être fait avec un foncteur simple (car il combine deux formes en une), ni une monade, car avec une monade, vous pouvez exprimer:
userData = do
name <- field "Name"
address <- field $ name ++ "'s address"
return (User name address)
qui ne peut pas être rendu, car le nom du deuxième champ ne peut pas être connu sans avoir déjà la réponse du premier. Je suis presque sûr qu'il existe une bibliothèque qui implémente cette idée de formulaires - j'ai roulé plusieurs fois la mienne pour tel ou tel projet.
L'autre chose intéressante à propos des foncteurs applicatifs est qu'ils composent. Plus précisément, le foncteur de composition:
newtype Compose f g x = Compose (f (g x))
est applicable chaque fois que f
et g
le sont. La même chose ne peut pas être dite pour les monades, ce qui a créé toute l'histoire du transformateur de monade qui est compliquée à certains égards désagréables. Les applications sont super propres de cette façon, et cela signifie que vous pouvez créer la structure d'un type dont vous avez besoin en vous concentrant sur de petits composants composables.
Récemment, l'extension ApplicativeDo
est apparue dans GHC, ce qui vous permet d'utiliser la notation do
avec des applications, ce qui allège la complexité de la notation, tant que vous ne faites rien de monady.
Un bon exemple: l'analyse applicative.
Voir [haskell du monde réel] ch16 http://book.realworldhaskell.org/read/using-parsec.html#id652517
Voici le code de l'analyseur avec la do-notation:
-- file: ch16/FormApp.hs
p_hex :: CharParser () Char
p_hex = do
char '%'
a <- hexDigit
b <- hexDigit
let ((d, _):_) = readHex [a,b]
return . toEnum $ d
En utilisant functor, faites-le plus court:
-- file: ch16/FormApp.hs
a_hex = hexify <$> (char '%' *> hexDigit) <*> hexDigit
where hexify a b = toEnum . fst . head . readHex $ [a,b]
"lever" peut masquer les détails sous-jacents d'un code répétitif. alors vous pouvez simplement utiliser moins de mots pour raconter l'histoire exacte et précise.
Je suggère également de jeter un œil à this
À la fin de l'article, il y a un exemple
import Control.Applicative
hasCommentA blogComments =
BlogComment <$> lookup "title" blogComments
<*> lookup "user" blogComments
<*> lookup "comment" blogComments
Ce qui illustre plusieurs caractéristiques du style de programmation applicative.