Haskell: Comment se prononce <*>? [fermé]

109

Comment prononcez-vous ces fonctions dans la classe de types Applicative:

(<*>) :: f (a -> b) -> f a -> f b
(*>)  :: f a -> f b -> f b
(<*)  :: f a -> f b -> f a

(Autrement dit, s'ils n'étaient pas des opérateurs, comment pourraient-ils être appelés?)

En purepassant , si vous pouviez renommer quelque chose de plus convivial pour les non-mathématiciens, comment l'appelleriez-vous?

J Cooper
la source
6
@J Cooper ... pourriez-vous entendre comment nous le prononçons? :) Vous voudrez peut-être poster une demande sur meta.stackoverflow.com pour une fonction d'enregistrement et de lecture de la voix :).
Kiril
8
Il est prononcé "Bon sang, ils manquaient vraiment d'opérateurs, n'est-ce pas?" En outre, un bon nom pour purepourrait être makeApplicative.
Chuck
@Lirik, Eh bien, je suppose qu'en prononçant je veux dire "whaddya appelle cette chose" :) @Chuck, postez votre puresuggestion comme réponse et je vous donnerai un vote favorable
J Cooper
6
(<*>) est la version Control.Applicative de "ap" de Control.Monad, donc "ap" est probablement le nom le plus approprié.
Edward KMETT
11
J'appellerais ça un cyclope, mais c'est juste moi.
RCIX

Réponses:

245

Désolé, je ne connais pas vraiment mes maths, donc je suis curieux de savoir comment prononcer les fonctions dans la classe de types Applicative

Connaître vos maths, ou non, est largement hors de propos ici, je pense. Comme vous le savez probablement, Haskell emprunte quelques bouts de terminologie à divers domaines des mathématiques abstraites, notamment la théorie des catégories , d'où nous obtenons des foncteurs et des monades. L'utilisation de ces termes dans Haskell diverge quelque peu des définitions mathématiques formelles, mais ils sont généralement assez proches pour être de toute façon de bons termes descriptifs.

La Applicativeclasse de types se situe quelque part entre Functoret Monad, on s'attend donc à ce qu'elle ait une base mathématique similaire. La documentation du Control.Applicativemodule commence par:

Ce module décrit une structure intermédiaire entre un foncteur et une monade: il fournit des expressions pures et du séquençage, mais pas de liaison. (Techniquement, un puissant foncteur monoïdal laxiste.)

Hmm.

class (Functor f) => StrongLaxMonoidalFunctor f where
    . . .

Pas aussi accrocheur que Monadje pense.

Ce à quoi tout cela se résume fondamentalement, c'est que Applicativecela ne correspond à aucun concept particulièrement intéressant mathématiquement, donc il n'y a pas de termes prêts à l'emploi qui traînent qui capturent la façon dont il est utilisé dans Haskell. Alors, mettez les calculs de côté pour le moment.


Si nous voulons savoir comment appeler, (<*>)il peut être utile de savoir ce que cela signifie fondamentalement.

Alors , quel est avec Applicative, de toute façon, et pourquoi ne nous l' appelons ça?

Ce qui Applicativerevient en pratique est un moyen de soulever des fonctions arbitraires en a Functor. Considérez la combinaison de Maybe(sans doute le plus simple non trivial Functor) et Bool(de même le type de données non trivial le plus simple).

maybeNot :: Maybe Bool -> Maybe Bool
maybeNot = fmap not

La fonction fmapnous permet de passer notdu travail Boolau travail Maybe Bool. Mais que faire si nous voulons soulever (&&)?

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 Boolen introduire un autre Maybepar l'arrière ...

maybeAnd'' :: Maybe Bool -> Bool -> Maybe Bool
maybeAnd'' x y = fmap ($ y) (fmap (&&) x)

... mais ce n'est pas bon. D'une part, c'est faux. Pour autre chose, c'est moche . Nous pourrions continuer d'essayer, mais il s'avère qu'il n'y a aucun moyen de lever une fonction d'arguments multiples pour travailler sur un arbitraireFunctor . Énervant!

D'un autre côté, nous pourrions le faire facilement si nous Maybeutilisions l' Monadinstance de:

maybeAnd :: Maybe Bool -> Maybe Bool -> Maybe Bool
maybeAnd x y = do x' <- x
                  y' <- y
                  return (x' && y')

Maintenant, c'est beaucoup de tracas juste pour traduire une fonction simple - ce qui est la raison pour laquelle Control.Monadfournit une fonction pour le faire automatiquement, liftM2. Le 2 dans son nom fait référence au fait qu'il fonctionne sur des fonctions d'exactement deux arguments; des fonctions similaires existent pour les fonctions d'argument 3, 4 et 5. Ces fonctions sont meilleures , mais pas parfaites, et spécifier le nombre d'arguments est laid et maladroit.

Ce qui nous amène à l' article qui a introduit la classe de type Applicatif . Les auteurs y font essentiellement deux observations:

  • Lever des fonctions multi-arguments dans un Functorest une chose très naturelle à faire
  • Cela ne nécessite pas les capacités complètes d'un Monad

L'application de fonction normale est écrite par une simple juxtaposition de termes, donc pour rendre «l'application soulevée» aussi simple et naturelle que possible, l'article présente des opérateurs infixes pour remplacer l'application, soulevés dans leFunctor , et une classe de type pour fournir ce qui est nécessaire pour cela .

Tout cela nous amène au point suivant: (<*>)représente simplement une application de fonction - alors pourquoi la prononcer différemment de celle de l '«opérateur de juxtaposition» d'espaces blancs?

Mais si ce n'est pas très satisfaisant, on peut observer que le Control.Monadmodule fournit également une fonction qui fait la même chose pour les monades:

ap :: (Monad m) => m (a -> b) -> m a -> m b

apest, bien sûr, l'abréviation de «appliquer». Puisque tout Monadpeut être Applicative, et apn'a besoin que du sous-ensemble de fonctionnalités présentes dans ce dernier, nous pouvons peut-être dire que si ce (<*>)n'était pas un opérateur, il devrait être appelé ap.


On peut aussi aborder les choses dans l'autre sens. L' Functoropération de levage est appelée fmapcar c'est une généralisation de l' mapopération sur les listes. Quel genre de fonction sur les listes fonctionnerait-il (<*>)? Il y a ce qui apfait sur les listes, bien sûr, mais ce n'est pas particulièrement utile en soi.

En fait, il existe une interprétation peut-être 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 tellement tentant dans l'idée d'aligner les listes en parallèle, en appliquant chaque fonction de la première à l'élément correspondant de la seconde. 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 amende Applicative, auquel cas (<*>)devient une façon d' enchaîner une version généralisée de zipWith, alors peut-être pouvons-nous imaginer l'appeler fzipWith?


Cette idée de fermeture éclair nous amène en fait à boucler la boucle. Rappelez-vous ce truc de maths plus tôt, à propos des foncteurs monoïdaux? Comme son nom l'indique, il s'agit d'un moyen de combiner la structure des monoïdes et des foncteurs, qui sont tous deux des classes de type Haskell familières:

class Functor f where
    fmap :: (a -> b) -> f a -> f b

class Monoid a where
    mempty :: a
    mappend :: a -> a -> a

À quoi ressembleraient-ils si vous les mettiez ensemble dans une boîte et la secouiez un peu? De Functornous allons garder l'idée d'une structure indépendante de son paramètre de type , et de Monoidnous garderons la forme globale 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 vraiment "vide" Functor, et nous ne pouvons pas évoquer une valeur d'un type arbitraire, nous allons donc corriger le type de mfEmptyas f ().

Nous ne voulons pas non plus forcer mfAppendle besoin d'un paramètre de type cohérent, alors maintenant nous avons ceci:

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ()
    mfAppend :: f a -> f b -> f ?

À quoi sert le type de résultat mfAppend? Nous avons deux types arbitraires dont nous ne savons rien, donc nous n'avons pas beaucoup d'options. Le plus judicieux est de garder les deux:

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ()
    mfAppend :: f a -> f b -> f (a, b)

À quel point mfAppendest maintenant clairement une version généralisée de zipon lists, et nous pouvons reconstruire Applicativefacilement:

mfPure x = fmap (\() -> x) mfEmpty
mfApply f x = fmap (\(f, x) -> f x) (mfAppend f x)

Cela nous montre également qu'il pureest lié à l'élément d'identité de a Monoid, donc d'autres bons noms peuvent ê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 soit la lire comme "ap" ou "appliquer", soit l'éliminer entièrement comme vous le feriez pour une application de fonction normale.
  • (<*>)généralise aussi grossièrement zipWithsur les listes, de sorte que vous pouvez le lire en tant que "foncteurs zip avec", de la même manière fmapque "mapper un foncteur avec".

Le premier est plus proche de l'intention de la Applicativeclasse de type - comme son nom l'indique - c'est donc ce que je recommande.

En fait, j'encourage l'utilisation libérale, et la non-prononciation, de tous les opérateurs d'application levés :

  • (<$>), qui lève une fonction à un seul argument en un Functor
  • (<*>), qui enchaîne une fonction multi-arguments via un Applicative
  • (=<<), qui lie une fonction qui entre un Monadsur un calcul existant

Tous les trois sont, au fond, juste une application de fonction régulière, un peu épicée.

CA McCann
la source
6
@Colin Cochrane: Etes-vous sûr de ne pas avoir mal orthographié "long-winded" là-bas? :) Mais bon, je vais le prendre! Je pense toujours que Applicativeet le style idiomatique fonctionnel qu'il promeut ne suscite pas assez d'amour, donc je n'ai pas pu résister à la chance de vanter un peu ses vertus comme un moyen d'expliquer comment je (ne) prononce pas (<*>).
CA McCann
+1! N'hésitez pas à être aussi long sur stackoverflow.com/questions/2104446/…
Greg Bacon
6
Est-ce que Haskell avait du sucre de syntaxe pour Applicative's! Quelque chose comme [| f a b c d |](comme suggéré par le papier original). Ensuite, nous n'aurions pas besoin du <*>combinateur et vous feriez référence à une telle expression comme un exemple d '"application de fonction dans un contexte fonctionnel"
Tom Crockett
1
@FredOverflow: Non, je voulais dire Monad. Ou Functorou Monoidou toute autre chose qui a un terme bien établi comportant moins de trois adjectifs. «Applicatif» est simplement un nom sans intérêt, quoique raisonnablement descriptif, appliqué à quelque chose qui en avait plutôt besoin.
CA McCann
1
@pelotom: voir [ stackoverflow.com/questions/12014524/… où des gens gentils m'ont montré deux façons d'obtenir presque cette notation.
AndrewC
21

Comme je n'ai aucune ambition d'améliorer la réponse technique de CA McCann , je vais aborder la plus pelucheuse:

Si vous pouviez renommer purequelque 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 criées contre la Monadversion, appelée " return", je propose un autre nom, qui suggère sa fonction d'une manière qui peut satisfaire le plus impératif des programmeurs impératifs et le plus fonctionnel de ... eh bien, je l' espère, tout le monde peut se plaindre de la même chose: inject.

Prenez une valeur. « Injecter » dans le Functor, Applicative, Monadou ce que vous voudrez. Je vote pour " inject" et j'ai approuvé ce message.

BMeph
la source
4
Je penche généralement vers quelque chose comme «unité» ou «ascenseur», mais ceux-ci ont déjà trop d'autres significations dans Haskell. injectest un excellent nom et probablement meilleur que le mien, bien que comme note secondaire mineure, "inject" est utilisé dans - je pense - Smalltalk et Ruby pour une méthode de pliage à gauche. Je n'ai jamais compris ce choix de nom, cependant ...
CA McCann
3
C'est un fil très ancien, mais je pense que injectdans Ruby & Smalltalk est utilisé parce que c'est comme si vous "injectiez" un opérateur entre chaque élément de la liste. Du moins, c'est comme ça que j'y ai toujours pensé.
Jonathan Sterling
1
Pour encore ramasser ce vieux fil de côté: Vous n'êtes pas injecter les opérateurs, vous remplacez (éliminant) les constructeurs qui sont déjà là. (Vu de l'autre côté, vous injectez d' anciennes données dans un nouveau type.) Pour les listes, l'élimination est juste foldr. (Vous remplacez (:)et [], où (:)prend 2 arguments et []est une constante, d'où foldr (+) 0 (1:2:3:[])1+2+3+0.) Sur Boolc'est juste if- then- else(deux constantes, choisissez-en une) et pour Maybeça s'appelle maybe... Haskell n'a pas de nom / fonction unique pour cela, car tous ont des types différents (en général elim n'est que récursion / induction)
personne
@CAMcCann Smalltalk tire ce nom d' une chanson d'Arlo Guthrie sur le projet de guerre du Vietnam, dans laquelle de jeunes hommes malheureux ont été rassemblés, sélectionnés, parfois rejetés et autrement injectés.
Tom Anderson
7

En bref:

  • <*>vous pouvez l'appeler appliquer . Donc, Maybe f <*> Maybe apeut être prononcé comme appliqué Maybe fsurMaybe a .

  • Vous pouvez renommer pureen of, comme le font de nombreuses bibliothèques JavaScript. Dans JS, vous pouvez créer un Maybeavec Maybe.of(a).

En outre, le wiki de Haskell a une page sur la prononciation des opérateurs de langue ici

Marcelo Lazaroni
la source
3
(<*>) -- Tie Fighter
(*>)  -- Right Tie
(<*)  -- Left Tie
pure  -- also called "return"

Source: Programmation Haskell à partir des premiers principes , par Chris Allen et Julie Moronuki

dmvianna
la source
Pour autant que je sache, ces noms n'ont pas vraiment compris.
dfeuer
@dfeuer attend la prochaine génération de Haskeller qui utilisent ce livre comme matériel d'apprentissage principal.
dmvianna
1
Ça pourrait arriver. Les noms sont horribles, cependant, car ils n'ont aucun lien avec les significations.
dfeuer
1
@dfeuer, je ne vois nulle part une bonne solution. "ap" / "apply" est aussi vague que "tie fighter". Tout est application de fonction. Cependant, un nom qui sort du bleu pourrait acquérir un sens par l'usage. «Apple» est un bon exemple. À propos, Monad return est purement Applicatif. Aucune invention ici.
dmvianna