Comment prononcez-vous ces fonctions dans la classe Applicative:
() :: f (a -> b) -> fa -> fb (*>) :: fa -> fb -> fb ( fb -> fa
(C’est-à-dire, s’ils n’étaient pas des opérateurs, comment pourraient-ils être appelés?)
En guise de note, si vous pouviez renommer le pure
en quelque chose de plus convivial pour les non-mathématiciens, comment l’appelleriez-vous?
Désolé, je ne connais pas vraiment mes maths, alors je suis curieux de savoir comment prononcer les fonctions de la classe Applicative
Connaître vos mathématiques, ou pas, est en grande partie sans importance ici, je pense. Comme vous le savez probablement, Haskell emprunte quelques éléments de terminologie dans différents domaines des mathématiques abstraites, notamment la théorie des catégories , d’où proviennent les foncteurs et les monades. L’utilisation de ces termes dans Haskell s’écarte quelque peu des définitions mathématiques formelles, mais ils sont généralement assez proches pour être de bons termes descriptifs.
La classe de type Applicative
situe quelque part entre Functor
et Monad
. On peut donc s’attendre à ce qu’elle repose sur une base mathématique similaire. La documentation du module Control.Applicative
commence par:
Ce module décrit une structure intermédiaire entre un foncteur et une monade: il fournit des expressions et un séquençage purs, mais sans liaison. (Techniquement, un foncteur monoïdal fort laxiste.)
Hmm.
class (Functor f) => StrongLaxMonoidalFunctor f where . . .
Pas tout à fait aussi accrocheur que Monad
, je pense.
Ce que tout cela revient à dire, c’est que Applicative
ne correspond à aucun concept particulièrement intéressant du sharepoint vue mathématique, de sorte qu’il n’y a pas de termes prêts à être utilisés pour capturer la manière dont ils sont utilisés dans Haskell. Alors, mettez les maths de côté pour le moment.
Si nous voulons savoir quoi appeler (<*>)
il peut être utile de savoir ce que cela signifie fondamentalement.
Alors quoi de neuf avec Applicative
, de toute façon, et pourquoi l’appelons-nous comme ça?
Ce qui constitue une Applicative
dans la pratique est un moyen de Functor
des fonctions arbitraires dans un Functor
. Considérons la combinaison de Maybe
(sans doute le Functor
non-sortingvial le plus simple) et de Bool
(de même le type de données non sortingvial le plus simple).
maybeNot :: Maybe Bool -> Maybe Bool maybeNot = fmap not
La fonction fmap
nous permet de not
travailler sur Bool
pour travailler sur Maybe Bool
. Mais si on veut lever (&&)
?
maybeAnd' :: Maybe Bool -> Maybe (Bool -> Bool) maybeAnd' = fmap (&&)
Eh bien, ce n’est pas ce que nous voulons du tout ! En fait, c’est à peu près inutile. Nous pouvons essayer d’être intelligents et introduire un autre Bool
dans Maybe
travers le dos …
maybeAnd'' :: Maybe Bool -> Bool -> Maybe Bool maybeAnd'' xy = fmap ($ y) (fmap (&&) x)
… mais ce n’est pas bon. D’une part, c’est faux. Pour une autre chose, c’est moche . Nous pourrions continuer à essayer, mais il s’avère qu’il n’y a aucun moyen de lever une fonction de plusieurs arguments pour travailler sur un Functor
arbitraire . Ennuyeux!
Par contre, on pourrait le faire facilement si on utilisait l’instance Monad
Maybe
:
maybeAnd :: Maybe Bool -> Maybe Bool -> Maybe Bool maybeAnd xy = do x' <- x y' <- y return (x' && y')
Maintenant, c'est beaucoup de tracas juste pour traduire une fonction simple - c'est pourquoi Control.Monad
fournit une fonction pour le faire automatiquement, liftM2
. Le 2 dans son nom fait référence au fait qu'il fonctionne sur des fonctions de deux arguments exactement; des fonctions similaires existent pour 3, 4 et 5 fonctions d'argument. Ces fonctions sont meilleures , mais pas parfaites, et la spécification du nombre d’arguments est moche et maladroite.
Ce qui nous amène au papier qui a introduit la classe de type Applicative . Les auteurs y font essentiellement deux observations:
Functor
fonctions multi-arguments dans un Functor
est une chose très naturelle à faire Monad
L'application de fonction normale est écrite par simple juxtaposition de termes, afin de rendre "l'application levée" aussi simple et naturelle que possible, le papier présente des opérateurs infixes pour l'application, levés dans le Functor
et une classe de type pour fournir cette.
Tout cela nous amène au point suivant: (<*>)
représente simplement l’application de la fonction - alors pourquoi la prononcer différemment de l’opérateur de juxtaposition des espaces?
Mais si ce n'est pas très satisfaisant, nous pouvons observer que le module Control.Monad
fournit également une fonction qui fait la même chose pour les monades:
ap :: (Monad m) => m (a -> b) -> ma -> mb
Où ap
est bien sûr court pour "appliquer". Comme toute Monad
peut être Applicative
, et que ap
n'a besoin que du sous-ensemble de caractéristiques présentes dans ce dernier, nous pouvons peut-être dire que si (<*>)
n'était pas un opérateur, il devrait être appelé ap
.
Nous pouvons également aborder les choses dans l'autre sens. L'opération de levage de Functor
s'appelle fmap
car il s'agit d'une généralisation de l'opération map
sur les listes. Quelle sorte de fonction sur les listes fonctionnerait comme (<*>)
? Il y a bien sûr ce que fait l' ap
sur les listes, mais ce n'est pas particulièrement utile en soi.
En fait, il existe peut-être une interprétation plus naturelle des listes. Qu'est-ce qui vous vient à l'esprit lorsque vous regardez la signature de type suivante?
listApply :: [a -> b] -> [a] -> [b]
Il y a quelque chose de très tentant dans l'idée de doubler les listes en parallèle, en appliquant chaque fonction du premier à l'élément correspondant du second. Malheureusement pour notre vieil ami Monad
, cette opération simple viole les lois de la monade si les listes sont de longueurs différentes. Mais cela fait une bonne Applicative
, auquel cas (<*>)
devient un moyen de lier une version généralisée de zipWith
, alors peut-être pouvons-nous imaginer l'appeler fzipWith
?
Cette idée de fermeture nous amène en fait à un cercle complet. Rappelez-vous des trucs de maths plus tôt, à propos des foncteurs monoïdaux? Comme son nom l’indique, il s’agit d’une manière de combiner la structure des monoides et des foncteurs, deux classes familières de type Haskell:
class Functor f where fmap :: (a -> b) -> fa -> fb class Monoid a where mempty :: a mappend :: a -> a -> a
À quoi ressembleraient-ils si vous les plaçiez dans une boîte et les secouiez un peu? À partir de Functor
nous garderons l'idée d'une structure indépendante de son paramètre de type et, à partir de Monoid
nous conserverons la forme générale des fonctions:
class (Functor f) => MonoidalFunctor f where mfEmpty :: f ? mfAppend :: f ? -> f ? -> f ?
Nous ne voulons pas supposer qu'il existe un moyen de créer un Functor
vraiment "vide", et nous ne pouvons pas évoquer une valeur de type arbitraire, nous allons donc corriger le type de mfEmpty
tant que f ()
.
Nous ne voulons pas non plus forcer mfAppend
à avoir besoin d'un paramètre de type cohérent, nous avons maintenant ceci:
class (Functor f) => MonoidalFunctor f where mfEmpty :: f () mfAppend :: fa -> fb -> f ?
Quel est le type de résultat pour mfAppend
? Nous avons deux types arbitraires dont nous ne soaps rien, donc nous n'avons pas beaucoup d'options. La chose la plus sensée est de garder les deux:
class (Functor f) => MonoidalFunctor f where mfEmpty :: f () mfAppend :: fa -> fb -> f (a, b)
À ce stade, mfAppend
est maintenant clairement une version généralisée de zip
sur les listes, et nous pouvons reconstruire facilement Applicative
:
mfPure x = fmap (\() -> x) mfEmpty mfApply fx = fmap (\(f, x) -> fx) (mfAppend fx)
Cela nous montre également que pure
est liée à l'élément d'identité d'un Monoid
, donc d'autres bons noms pourraient être tout ce qui suggère une valeur unitaire, une opération nulle ou autre.
C'était long, donc pour résumer:
(<*>)
est juste une application de fonction modifiée, vous pouvez donc la lire comme "ap" ou "appliquer", ou l'éliminer complètement comme vous le feriez pour une application normale. (<*>)
généralise aussi grossièrement zipWith
sur les listes, donc vous pouvez le lire comme "zip functors avec", comme si vous lisiez fmap
comme "mapper un foncteur avec". Le premier est plus proche de l'intention de la classe de type Applicative
- comme son nom l'indique -, c'est ce que je recommande.
En fait, j'encourage l'utilisation libérale et la non-prononciation de tous les opérateurs d'applications soulevés :
(<$>)
, qui soulève une fonction à argument unique dans un Functor
(<*>)
, qui enchaîne une fonction multi-argument via un Applicative
(=<<)
, qui lie une fonction qui entre dans une Monad
sur un calcul existant Tous les trois sont, au fond, juste une application de fonction régulière, un peu épicée.
Comme je n’ai aucune ambition d’améliorer la réponse technique de CA McCann , je me pencherai sur celle qui est la plus délicate:
Si vous pouviez renommer le
pure
en quelque chose de plus convivial pour les podunks comme moi, comment l’appelleriez-vous?
Comme alternative, d’autant plus qu’il n’y a pas de fin à l’angoisse et à la trahison constantes contre la version de Monad
, appelée ” return
“, je propose un autre nom, qui suggère sa fonction d’une manière qui puisse satisfaire le plus impératif programmeurs impératifs, et les plus fonctionnels de … eh bien, espérons que tout le monde peut se plaindre de: inject
.
Prenez une valeur. “Injecter” dans le Functor
, Applicative
, Monad
ou quoi-vous. Je vote pour ” inject
” et j’ai approuvé ce message.
J’ai toujours aimé l’ wrap
. Prenez une valeur et emballez-la dans un foncteur, applicatif, Monad. Cela fonctionne aussi bien lorsqu’il est utilisé dans une phrase avec des instances concrètes: []
, Maybe
, etc. “Il prend une valeur et l’enveloppe dans un X
“.
(<*>) -- Tie Fighter (*>) -- Right Tie (<*) -- Left Tie pure -- also called "return"
Source: Haskell Programming from First Principles , par Chris Allen et Julie Moronuki
En bref:
<*>
vous pouvez l’appeler appliquer . Alors Maybe f <*> Maybe a
peut être prononcé comme applicable Maybe f
sur Maybe a
.
Vous pouvez renommer pure
comme de nombreuses bibliothèques JavaScript. Dans JS, vous pouvez créer un Maybe
avec Maybe.of(a)
.
De plus, le wiki de Haskell a une page sur la prononciation des opérateurs de langue ici