在哈斯克尔有 fmap 有什么意义?

无论我在哪里尝试使用 mapfmap都同样有效。为什么 Haskell 的创建者觉得需要 map函数?不能只是目前所知的 fmapfmap可以从语言中删除?

30932 次浏览

我想作一个回答,提请注意 奥古斯特斯的评论:

事实上不是这样的。在 Haskell 1.3中,映射的类型被推广为覆盖函子。也就是说,在 Haskell 中1.3 fmap 被称为 map。然后在 Haskell 1.4中恢复这个更改,并引入 fmap。这种变化的原因是教学上的; 当教初学者 Haskell 时,非常一般的地图类型使得错误信息更难理解。在我看来,这不是解决问题的正确方法。

Haskell 98被一些 Haskeller (包括我)视为一种倒退,之前的版本定义了一个更加抽象和一致的库。好吧。

引用 https://wiki.haskell.org/Typeclassopedia#FunctorFunctor文档

你可能会问为什么我们需要一个单独的 map函数 去掉当前仅支持列表的 map函数,并将 fmap重命名为 map 这是个好问题,通常的论点是 有人刚刚学习 Haskell,当使用 map不正确,会很多 与其看到关于 Functor的错误,不如看到关于列表的错误。

它们在应用程序站点上看起来一样,但是当然它们是不同的。当您将这两个函数 mapfmap应用到一个值列表时,它们将产生相同的结果,但这并不意味着它们用于相同的目的。

运行一个 GHCI 会话(Glasgow Haskell Compiler Interactive)来查询关于这两个函数的信息,然后查看它们的实现,您将发现许多不同之处。

地图

查询有关 map的信息的 GHCI

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

您将看到它被定义为一个高阶函数,该函数适用于任何类型的 a值列表,产生任何类型的 b值列表。虽然多态(在上面的定义中,ab代表任何类型) ,但是 map功能的目的是应用于一个 价值观列表,这只是哈斯克尔许多其他数据类型中的一种可能的数据类型。map函数不能应用于不是值列表的内容。

正如您可以从 GHC 基地源代码中读到的,map函数的实现如下

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

它利用模式匹配将头部(x)从列表的尾部(xs)拉出,然后使用 :(con)值构造函数构造一个新列表,这样就可以将 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 基地源代码可以看出,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、 tuple、 list 等)。由于 “价值观清单”数据类型也是一个函数(因为它为它提供了一个实现) ,那么 fmap也可以应用于生成与 map非常相同的结果。

map  (+3) [1..5]
fmap (+3) (Just 15)
fmap (+3) (5, 7)