Por que temos map, fmap e 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

Por que temos três funções diferentes que fazem essencialmente a mesma coisa?

fredoverflow
fonte
32
História, principalmente. fmap é diferente de map por razões pedagógicas, liftM é diferente de fmap por razões históricas (ou seja, Functor não é uma superclasse de
Monad
12
Ah, e apenas para ser claro: eles não fazem "essencialmente" a mesma coisa. Ambos mape liftMcertamente devem fazer exatamente a mesma coisa que fmap.
CA McCann de
2
Embora fmape liftMfaça exatamente a mesma coisa, mapé claro que é apenas um caso especial deles, ou seja, algo diferente. fmap id getLineestá bem digitado, mas map id getLinenão.
Thorsten

Respostas:

91

mapexiste para simplificar as operações em listas e por razões históricas (veja Qual é o objetivo do mapa em Haskell, quando há fmap? ).

Você pode perguntar por que precisamos de uma função de mapa separada. Por que não simplesmente acabar com a função de mapa somente de lista atual e renomear fmap para map em vez disso? Bem, essa é uma boa pergunta. O argumento usual é que alguém que acabou de aprender Haskell, ao usar o mapa incorretamente, prefere ver um erro sobre listas do que sobre Functors.

- Typeclassopedia , página 20

fmape liftMexistem porque as mônadas não eram automaticamente functores em Haskell:

O fato de termos fmap e liftM é uma consequência infeliz do fato de que a classe do tipo Monad não requer uma instância de Functor, embora matematicamente falando, toda mônada é um functor. No entanto, fmap e liftM são essencialmente intercambiáveis, uma vez que é um bug (em um sentido social e não técnico) para qualquer tipo ser uma instância de Monad sem também ser uma instância de Functor.

- Typeclassopedia , página 33

Editar: história de agustuss mape fmap:

Não é assim que acontece. O que aconteceu foi que o tipo de mapa foi generalizado para cobrir o Functor em Haskell 1.3. Ou seja, em Haskell 1.3 fmap era chamado de mapa. Esta mudança foi então revertida no Haskell 1.4 e o fmap foi introduzido. A razão para essa mudança foi pedagógica; ao ensinar Haskell para iniciantes, o tipo muito geral de mapa tornava as mensagens de erro mais difíceis de entender. Na minha opinião, essa não era a maneira certa de resolver o problema.

- Qual é o ponto do mapa em Haskell, quando há fmap?

li.davidm
fonte
13
E, da minha perspectiva como alguém que encontrou Haskell pela primeira vez mais de uma década depois que a mudança que @augustss descreve foi feita, e passou muito tempo ajudando pessoas que estão aprendendo o idioma agora, não está nada claro se isso ajudou em de qualquer forma. Certamente não o suficiente para compensar a redundância inútil (que por si só leva as pessoas a fazerem perguntas como esta); a Functorclasse é muito comum para ser ignorada e os iniciantes costumam ser confundidos com mensagens de erro!
CA McCann de
10
Não podemos simplesmente remover liftM? Deixe o código quebrar, quem se importa, normalmente leva menos de 2 dias para o código ser corrigido no github e depois carregado no hackage. Ou estou sendo selvagem e louco?
Tarrasch,
1
@Tarrasch: nem todo mundo usa o github, nem todos os pacotes têm um ótimo histórico de atualização no tempo, e eu, pelo menos, tento usar liftMenquanto estou em um bloco do que fmapporque ele se encaixa melhor quando uso liftM2, etc. também.
ivanm
1
@ L01man pessoas trabalharam nisso; consulte stackoverflow.com/questions/5730270/… e pelo menos para classes numéricas, existem alternativas: hackage.haskell.org/packages/archive/numeric-prelude/0.3.0.2/…
li.davidm
1
@ L01man Sim, isso será corrigido em breve. A Proposta Applicative Mônada (AMP) parece que vai passar para a próxima versão do Haskell. GHC 7.8.3 tem um novo sinalizador --fwarn-amppara ajudar a atualizar o código existente para a transição.
recursion.ninja de