Haskell: Comment se prononce ?

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
  • Cela n'exige pas toutes les capacités d'une 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 

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