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

98

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

Кларк Гебель
источник
11
Я думаю, вы спрашиваете: «В чем смысл fmap в Haskell»?
zw324
12
Я знаю, в чем смысл fmap. Это для сопоставления функции с экземпляром Functor. Мне интересно узнать о цели специализации map.
Clark Gaebel

Ответы:

95

Я хотел бы дать ответ, чтобы привлечь внимание к комментарию Августы :

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

Haskell 98 рассматривается некоторыми Haskellers (включая меня) как шаг назад, поскольку в предыдущих версиях была определена более абстрактная и последовательная библиотека. Ну что ж.

Луки
источник
16
Эти шаги назад собраны и задокументированы где-нибудь? Было бы интересно посмотреть, что еще считается шагом назад, и есть ли для них лучшие решения.
Davorak
3
Он map and fmapсуществует уже давно - он был повторно разогрет в списке рассылки Haskell-prime в августе 2006 года - haskell.org/pipermail/haskell-prime/2006-August/thread.html . В качестве контрапункта я предпочитаю статус-кво. Мне кажется ценным наличие подмножества Haskell, примерно соответствующего Миранде. В Великобритании Миранда использовалась в качестве языка обучения для студентов-математиков, а не только для студентов-информатиков. Если эта ниша еще не потеряна для нефункционального языка (например, Mathematica), я не вижу Haskell с унифицированным mapзаполнением.
Стивен Тетли
35
И еще я хотел бы отметить, для тех, кто еще не знает, что август - это Леннарт Аугустссон, который во всех практических целях был частью сообщества Haskell еще до того, как Haskell появился, ср. История Haskell , поэтому данный комментарий ни в коем случае не является слухом!
CA McCann
3
На вики Haskell теперь есть страница Nitpicks, где упоминается эта проблема.
Алексей
Забавно, что это решение было принято из педагогических соображений, потому что я все время путал fmap с flatmap при изучении Haskell. Они должны были собрать вместе фокус-группу из n00b. :)
Дэнни Эндрюс
27

Цитата из Functorдокументации на https://wiki.haskell.org/Typeclassopedia#Functor

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

Андрей Бозантан
источник
1
Для меня это имеет большой смысл.
hbobenicio
25

На сайте приложения они выглядят одинаково, но, конечно, они разные. Когда вы применяете одну из этих двух функций mapили fmapк списку значений, они дадут одинаковый результат, но это не значит, что они предназначены для одной и той же цели.

Запустите сеанс GHCI (интерактивный компилятор Glasgow Haskell), чтобы запросить информацию об этих двух функциях, затем посмотрите на их реализации, и вы обнаружите много различий.

карта

Запросите GHCI информацию о map

Prelude> :info map
map :: (a -> b) -> [a] -> [b]   -- Defined in ‘GHC.Base’

и вы увидите, что она определена как функция высокого порядка, применимая к списку значений любого типа, в aрезультате чего получается список значений любого типа b. Хотя и полиморфна ( aи bв приведенном выше определении обозначают любой тип), mapфункция предназначена для применения к списку значений, который является лишь одним из возможных типов данных среди многих других в Haskell. mapФункция не может быть применена к чему - то, не является списком значений.

Как видно из исходного кода GHC.Base , mapфункция реализована следующим образом

map _ []     = []
map f (x:xs) = f x : map f xs

который использует сопоставление с образцом, чтобы вытащить голову (the x) из хвоста (the xs) списка, а затем создает новый список с помощью :конструктора значения (cons), чтобы добавить f x(прочтите его как «f, примененный к x» ) к рекурсии mapнад хвостом, пока список не станет пустым. Стоит отметить, что реализация mapфункции не зависит от какой-либо другой функции, а только от себя.

fmap

Теперь попробуйте запросить информацию, fmapи вы увидите совсем другое.

Prelude> :info fmap
class Functor (f :: * -> *) where
  fmap :: (a -> b) -> f a -> f b
  ...
  -- Defined in ‘GHC.Base’

На этот раз fmapопределяется как одна из функций, реализация которой должна быть обеспечена теми типами данных, которые хотят принадлежать к Functorклассу типов. Это означает, что может быть более одного типа данных, а не только тип данных «список значений» , способных обеспечить реализацию fmapфункции. Это fmapприменимо к гораздо большему набору типов данных: действительно, к функторам!

Как вы можете прочитать из исходного кода GHC.Base , возможная реализация fmapфункции обеспечивается Maybeтипом данных:

instance  Functor Maybe  where
  fmap _ Nothing       = Nothing
  fmap f (Just a)      = Just (f a)

и другая возможная реализация - это тот, который предоставляется типом данных с двумя кортежами

instance Functor ((,) a) where
  fmap f (x,y) = (x, f y)

и другая возможная реализация - это тот, который предоставляется типом данных списка (конечно!):

instance  Functor []  where
  fmap f xs = map f xs

который полагается на mapфункцию.

Вывод

mapФункция может быть применена ни к чему не более чем список значений (где значения являются любого типа) , в то время как fmapфункция может быть применена гораздо больше типов данных: все те , которые принадлежат к классу функтора (например maybes, кортежи, списки и т.д. ). Так как тип данных «список значений» также является функтором (потому что он обеспечивает его реализацию), то fmapего можно применять и к нему, что дает тот же результат, что и map.

map  (+3) [1..5]
fmap (+3) (Just 15)
fmap (+3) (5, 7)
Паоло Анджолетти
источник