Почему у нас есть map, fmap и 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

Почему у нас есть три разные функции, которые по сути делают одно и то же?

fredoverflow
источник
32
В основном история. fmap отличается от map по педагогическим причинам, liftM отличается от fmap по историческим причинам (а именно, Functor не является суперклассом Monad)
Луки
12
Да, и просто для ясности: они «по сути» не делают то же самое. Как mapи liftMдолжно, безусловно , делать точно то же самое , как fmap.
CA McCann
2
Хотя fmapи liftMделают абсолютно то же самое, mapконечно, это лишь их частный случай, т.е. нечто иное. fmap id getLineхорошо напечатан, тогда map id getLineкак нет.
Торстен

Ответы:

91

mapсуществует для упрощения операций со списками и по историческим причинам (см. В чем смысл карты в Haskell, когда есть fmap? ).

Вы можете спросить, зачем нам отдельная функция карты. Почему бы просто не отказаться от текущей функции отображения только списка и вместо этого переименовать fmap в map? Что ж, это хороший вопрос. Обычный аргумент состоит в том, что кто-то, кто только изучает Haskell, при неправильном использовании map, скорее увидит ошибку о списках, чем о функторах.

- Типклассопедия , стр.20

fmapи liftMсуществуют, потому что монады не были автоматически функторами в Haskell:

Тот факт, что у нас есть и fmap, и liftM, является неудачным следствием того факта, что класс типа Monad не требует экземпляра Functor, хотя с математической точки зрения каждая монада является функтором. Однако fmap и liftM по сути взаимозаменяемы, поскольку это ошибка (в социальном, а не в техническом смысле) для любого типа быть экземпляром Monad, но не быть также экземпляром Functor.

- Типеклассопедия , стр. 33

Изменить: история Agustuss mapи fmap:

На самом деле это не так. Произошло то, что тип карты был обобщен для покрытия Functor в Haskell 1.3. Т.е. в Haskell 1.3 fmap назывался map. Затем это изменение было отменено в Haskell 1.4 и был представлен fmap. Причина этого изменения была педагогической; при обучении Haskell новичков использование очень общего типа карты затрудняло понимание сообщений об ошибках. На мой взгляд, это был неправильный способ решения проблемы.

- В чем смысл карты в Haskell, когда есть fmap?

li.davidm
источник
14
И, с моей точки зрения, как человека, который впервые столкнулся с Haskell более чем через десять лет после изменения, описанного @augustss, и потратил много времени, помогая людям, изучающим язык сейчас, совсем не ясно, помогло ли это вообще тем не мение. Конечно, недостаточно, чтобы компенсировать бесполезную избыточность (которая сама по себе приводит к тому, что люди задают подобные вопросы); Functorкласс слишком часто , чтобы игнорировать, и новички часто путаются сообщения об ошибках в любом случае!
CA McCann,
11
Разве мы не можем просто удалить liftM? Пусть код ломается, кого это волнует, обычно требуется меньше 2 дней, чтобы код был исправлен на github, а затем загружен при взломе. Или я дикий и сумасшедший?
Tarrasch
1
@Tarrasch: не все используют github, не все пакеты имеют отличный послужной список для своевременного обновления, и я, например, предпочитаю использовать liftMв do-блоке, а не fmapпотому, что он лучше подходит, когда я использую liftM2, и т. Д. также.
ivanm
1
@ L01man люди работали над этим; см. stackoverflow.com/questions/5730270/… и, по крайней мере, для числовых классов есть альтернативы: hackage.haskell.org/packages/archive/numeric-prelude/0.3.0.2/…
li.davidm
1
@ L01man Да, это скоро будет исправлено. Предложение Applicative Monad Proposal (AMP) похоже перейдет в следующую версию Haskell. GHC 7.8.3 имеет новый флаг, --fwarn-ampпомогающий обновить существующий код для перехода.
recursion.ninja