Pourquoi avons-nous map, fmap et liftM?

102
map :: (a -> b) -> [a] -> [b]

fmap :: Functor f => (a -> b) -> f a -> f b

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

Pourquoi avons-nous trois fonctions différentes qui font essentiellement la même chose?

fredoverflow
la source
32
L'histoire, surtout. fmap est distinct de map pour des raisons pédagogiques, liftM distinct de fmap pour des raisons historiques (à savoir Functor n'étant pas une superclasse de Monad)
luqui
12
Oh, et juste pour être clair: ils ne font pas «essentiellement» la même chose. Les deux mapet liftMdevraient certainement faire exactement la même chose que fmap.
CA McCann
2
Bien que fmapet liftMfaire exactement la même chose, mapbien sûr, ce n'est qu'un cas particulier, c'est-à-dire quelque chose de différent. fmap id getLineest bien typé, alors que ce map id getLinen'est pas le cas.
Thorsten

Réponses:

91

mapexiste pour simplifier les opérations sur les listes et pour des raisons historiques (voir A quoi sert la carte dans Haskell, quand il y a fmap? ).

Vous pourriez vous demander pourquoi nous avons besoin d'une fonction de carte distincte. Pourquoi ne pas simplement supprimer la fonction de carte actuelle de liste uniquement et renommer fmap en carte à la place? Eh bien, c'est une bonne question. L'argument habituel est que quelqu'un qui vient d'apprendre Haskell, lorsqu'il n'utilise pas correctement map, préfère de loin voir une erreur sur les listes plutôt que sur les Functors.

- Typeclassopedia , page 20

fmapet liftMexistent parce que les monades n'étaient pas automatiquement des foncteurs dans Haskell:

Le fait que nous ayons à la fois fmap et liftM est une conséquence malheureuse du fait que la classe de type Monad ne nécessite pas d'instance Functor, même si mathématiquement parlant, chaque monade est un foncteur. Cependant, fmap et liftM sont essentiellement interchangeables, car c'est un bogue (dans un sens social plutôt que technique) pour tout type d'être une instance de Monad sans être également une instance de Functor.

- Typeclassopedia , page 33

Edit: l'histoire d'Agustuss mapet fmap:

Ce n'est pas comme ça que ça se passe. Ce qui s'est passé, c'est que le type de carte a été généralisé pour couvrir Functor dans Haskell 1.3. Ie, dans Haskell 1.3 fmap s'appelait map. Ce changement a ensuite été annulé dans Haskell 1.4 et fmap a été introduit. La raison de ce changement était d'ordre pédagogique; lors de l'enseignement de Haskell aux débutants, le type très général de carte rend les messages d'erreur plus difficiles à comprendre. À mon avis, ce n'était pas la bonne façon de résoudre le problème.

- Quel est l'intérêt de la carte dans Haskell, quand il y a fmap?

li.davidm
la source
13
Et, de mon point de vue en tant que personne qui a rencontré Haskell pour la première fois plus d'une décennie après le changement décrit par @augustss, et qui a passé beaucoup de temps à aider les personnes qui apprennent la langue maintenant, il n'est pas du tout clair que cela ait même aidé à en tous cas. Certainement pas assez pour compenser la redondance inutile (qui elle-même conduit les gens à se poser des questions comme celle-ci); la Functorclasse est trop courante pour être ignorée, et les débutants sont souvent confus par les messages d'erreur de toute façon!
CA McCann
10
Ne pouvons-nous pas simplement supprimer liftM? Laissez le code se casser, peu importe, il faut généralement moins de 2 jours pour que le code soit corrigé sur github, puis téléchargé lors du piratage. Ou suis-je sauvage et fou?
Tarrasch
1
@Tarrasch: tout le monde n'utilise pas github, tous les packages n'ont pas d'excellents antécédents pour être mis à jour à temps, et pour ma part, j'ai tendance à utiliser liftMdans un do-block plutôt que fmapparce qu'il correspond mieux à quand j'utilise liftM2, etc. ainsi que.
ivanm
1
@ Les gens de L01man ont travaillé là-dessus; voir stackoverflow.com/questions/5730270/… et au moins pour les classes numériques, il existe des alternatives: hackage.haskell.org/packages/archive/numeric-prelude/0.3.0.2/…
li.davidm
1
@ L01man Oui, cela sera bientôt corrigé. La proposition de la monade applicative (AMP) semble passer dans la prochaine version de Haskell. GHC 7.8.3 a un nouvel indicateur --fwarn-amppour aider à mettre à jour le code existant pour la transition.
recursion.ninja