Всюду, где я пытался использовать map
, fmap
также работал. Почему создатели Haskell почувствовали необходимость в функции map
? Не может ли это быть только то, что в настоящее время известно как fmap
и fmap
может быть удалено с языка?
Какая точка карты в Haskell, когда есть fmap?
Ответ 1
Я хотел бы сделать ответ, чтобы обратить внимание на комментарий augustss выше:
Это не так, как это происходит. Случилось так, что тип карты был обобщен, чтобы охватить Functor в Haskell 1.3. I.e., в Haskell 1.3 fmap называлась картой. Затем это изменение было возвращено в Haskell 1.4 и введен fmap. Причина этого изменения была педагогической; при обучении Haskell новичкам очень общий тип карты затруднял понимание сообщений об ошибках. По-моему, это был неправильный способ решить проблему.
Haskell 98 рассматривается как шаг назад от некоторых Haskellers (включая меня), предыдущие версии определяли более абстрактную и согласованную библиотеку. О, хорошо.
Ответ 2
Цитата из документации Functor
на https://wiki.haskell.org/Typeclassopedia#Functor
Вы можете спросить, почему нам нужна отдельная функция
map
. Почему бы просто не сделать прочь с текущей функциейmap
только для списка и переименоватьfmap
вmap
вместо? Ну, это хороший вопрос. Обычным аргументом является то, что кто-то просто изучает Haskell при неправильном использованииmap
, скорее посмотрите ошибку о списках, чем примерноFunctor
.
Ответ 3
Они выглядят одинаково на сайте приложения, но они разные, конечно. Когда вы применяете одну из этих двух функций, map
или fmap
, к списку значений они будут давать одинаковый результат, но это не значит, что они предназначены для этой же цели.
Запустите сеанс GHCI (Glasgow Haskell Compiler Interactive), чтобы запросить информацию об этих двух функциях, а затем взгляните на их реализации, и вы обнаружите много различий.
Карта
Запрос GHCI для получения информации о map
Prelude> :info map
map :: (a -> b) -> [a] -> [b] -- Defined in ‘GHC.Base’
и вы увидите, что он определен как функция высокого порядка, применимая к списку значений любого типа a
, приводящему список значений любого типа b
. Хотя полиморфные (a
и b
в приведенном выше определении обозначают любой тип), функция map
предназначена для применения к списку значений, который является всего лишь одним возможным типом данных среди многие другие в Хаскелле. Функция map
не может быть применена к тому, что не является списком значений.
Как вы можете прочитать из GHC.Base исходного кода, функция map
реализована следующим образом
map _ [] = []
map f (x:xs) = f x : map f xs
который использует сопоставление шаблонов, чтобы вытащить головку (x
) с хвоста (xs
) списка, затем создает новый список с помощью конструктора значения :
(cons), поэтому prepend f x
(прочитайте его как "f, примененный к x" ) к рекурсии map
по хвосту, пока список не станет пустым. Стоит заметить, что реализация функции map
не зависит от какой-либо другой функции, а только от самого себя.
БПМЖ
Теперь попробуйте запросить информацию о 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
могут применяться гораздо больше типов данных: все те, которые принадлежат классу функтора ( например, майбы, кортежи, списки и т.д.). Так как тип данных "список значений" также является функтором (поскольку он обеспечивает реализацию для него), тогда fmap
может быть применен к тому же самому результату, что и map
.
map (+3) [1..5]
fmap (+3) (Just 15)
fmap (+3) (5, 7)