web-dev-qa-db-fra.com

Haskell: Comment se prononce <*>?

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 pourrait-on les appeler?)

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

100
J Cooper

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

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

La classe de type Applicative se situe quelque part entre Functor et Monad, donc on peut s'attendre à ce qu'elle ait 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 pas de liaison. (Techniquement, un foncteur monoïdal laxiste fort.)

Hmm.

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

Pas aussi accrocheur que Monad, je pense.

Tout cela revient à dire que Applicative 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 dans cette capture la façon dont il est utilisé à Haskell. Donc, mettez de côté les calculs pour l'instant.


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

Alors quoi de neuf avec Applicative, de toute façon, et pourquoi appelons-nous ainsi?

Ce que Applicative équivaut en pratique à un moyen de lever des fonctions arbitraires dans un Functor. Considérons la combinaison de Maybe (sans doute le plus simple non trivial Functor) et Bool (également le type de données non trivial le plus simple).

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

La fonction fmap nous permet de faire passer not du travail sur Bool au travail sur Maybe Bool. Mais que faire si nous voulons lever (&&)?

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

Eh bien, ce n'est pas du tout ce que nous voulons ! En fait, c'est à peu près inutile. Nous pouvons essayer d'être intelligents et de glisser un autre Bool dans Maybe par 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 une autre chose, c'est laid . Nous pourrions continuer d'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. Énervant!

D'un autre côté, nous pourrions le faire facilement si nous utilisions l'instance Maybe de Monad:

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 - 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 d'exactement deux arguments; des fonctions similaires existent pour les fonctions à 3, 4 et 5 arguments. Ces fonctions sont meilleures , mais pas parfaites, et spécifier le nombre d'arguments est laid et maladroit.

Ce qui nous amène au article qui a introduit la classe de type Applicative . Dans ce document, les auteurs font essentiellement deux observations:

  • Soulever des fonctions multi-arguments dans un Functor est une chose très naturelle à faire
  • Cela ne nécessite pas toutes les capacités d'un Monad

L'application de la fonction normale est écrite par simple juxtaposition de termes, donc pour rendre "l'application levée" aussi simple et naturelle que possible, le papier introduit des opérateurs infixes pour remplacer l'application, levés dans le Functor, 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 l'application de fonction - alors pourquoi la prononcer autrement que vous ne le faites "l'opérateur de juxtaposition"?

Mais si ce n'est pas très satisfaisant, on peut 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) -> m a -> m b

ap est, bien sûr, l'abréviation de "appliquer". Puisque tout Monad peut être Applicative, et ap n'a besoin que du sous-ensemble de fonctionnalités présentes dans ce dernier, on peut peut-être dire que si (<*>) N'était pas un opérateur, il devrait s'appeler ap.


Nous pouvons également aborder les choses dans l'autre sens. L'opération de levage Functor est appelée fmap car c'est une généralisation de l'opération map sur les listes. Quelle sorte de fonction sur les listes fonctionnerait comme (<*>)? Il y a ce que ap fait 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 examinez la signature de type suivante?

listApply :: [a -> b] -> [a] -> [b]

Il y a quelque chose de si 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 simple opération viole les lois de la monade si les listes sont de longueurs différentes. Mais cela fait un bon Applicative, auquel cas (<*>) Devient un moyen de enchaîner une version généralisée de zipWith, alors peut-être que nous peut imaginer l'appeler fzipWith?


Cette idée de fermeture éclair nous fait boucler la boucle. Rappelez-vous ces trucs mathématiques plus tôt, sur les foncteurs monoïdaux? Comme leur nom l'indique, ceux-ci sont 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 dans une boîte et les secouiez un peu? De Functor nous garderons l'idée d'une structure indépendante de son paramètre de type , et de Monoid nous garderons la forme globale de les 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 as f ().

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

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

Quel est le type de résultat pour mfAppend? Nous avons deux types arbitraires dont nous ne savons rien, nous n'avons donc pas beaucoup d'options. La chose la plus sensée est de simplement garder les deux:

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

À ce stade, mfAppend est désormais clairement une version généralisée de Zip sur les listes, et nous pouvons reconstruire Applicative facilement:

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

Cela nous montre également que pure est lié à l'élément d'identité d'un Monoid, donc d'autres bons noms pour cela pourraient être n'importe quoi suggérant 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'élire entièrement comme vous le feriez pour une application de fonction normale.
  • (<*>) Généralise également approximativement zipWith sur les listes, vous pouvez donc le lire comme "Zip fonctors with", de la même manière que lire fmap as " 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 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 à argument unique en Functor
  • (<*>), Qui enchaîne une fonction multi-arguments via un Applicative
  • (=<<), Qui lie une fonction qui entre un Monad sur un calcul existant

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

236
C. A. McCann

Comme je n'ai aucune ambition de m'améliorer Réponse technique de C. A. McCann , je vais aborder la plus moelleuse:

Si vous pouviez renommer 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 à la constante constante d'angoisse et de trahison contre la version Monad, 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, j'espère que tout le monde peut se plaindre de: inject.

Prenez une valeur. "Injectez" dans le Functor, Applicative, Monad, ou ce que vous avez. Je vote pour "inject" et j'ai approuvé ce message.

21
BMeph

En bref:

  • <*> Vous pouvez l'appeler appliquer . Ainsi Maybe f <*> Maybe a Peut être prononcé comme appliquer Maybe f Sur Maybe a.

  • Vous pouvez renommer pure en of, comme le font 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 langage ici

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

Source: Haskell Programming from First Principles , par Chris Allen et Julie Moronuki

3
dmvianna