我尝试过使用map
的所有地方,fmap
也可以使用。为什么Haskell的创建者觉得需要map
函数?难道不能只是当前所谓的fmap
和fmap
可以从语言中移除吗?
答案 0 :(得分:85)
我想回答引起对augustss's comment的注意:
实际上并不是这样的。发生的事情是,地图的类型被概括为涵盖Haskell 1.3中的Functor。即,在Haskell 1.3中,fmap被称为map。然后在Haskell 1.4中恢复此更改并引入了fmap。这种变化的原因是教学法;在向初学者教授Haskell时,非常一般的地图类型使得错误消息更难以理解。在我看来,这不是解决问题的正确方法。
Haskell 98被一些Haskellers(包括我)看作是一个倒退,以前的版本定义了一个更抽象和一致的库。哦,好吧。
答案 1 :(得分:26)
引用https://wiki.haskell.org/Typeclassopedia#Functor
上的Functor
文档
您可能会问为什么我们需要一个单独的
map
函数。为什么不这样做呢 离开当前仅限列表的map
功能,并将fmap
重命名为map
代替?嗯,这是一个很好的问题。通常的论点是 只是学习Haskell的人,在错误地使用map
时会有很多 而是看到有关列表的错误,而不是关于Functor
。
答案 2 :(得分:13)
它们在应用程序网站上看起来一样,但它们当然是不同的。当您将这两个函数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
函数旨在应用于值列表,这是在Haskell中只有一种可能的数据类型。 map
函数无法应用于不是值列表的内容。
正如您可以从GHC.Base源代码中读到的那样,map
函数实现如下
map _ [] = []
map f (x:xs) = f x : map f xs
利用模式匹配将头部(x
)拉离列表的尾部(xs
),然后使用:
构建新列表( cons)值构造函数,以便将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)
另一种可能的实现是由2元组数据类型提供的实现
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)