【发布时间】:2016-09-29 08:52:57
【问题描述】:
我的理解是map和fmap的区别在于后者可以返回一个函数?
我正在研究这个http://learnyouahaskell.com 的函子部分,有些解释有点不清楚。
Map 和 fmap 在以下方面的行为相同:
let exMap = map (+1) [1..5]
let exFMap = fmap (+1) [1..5]
什么是 fmap 返回函数的好例子?
【问题讨论】:
我的理解是map和fmap的区别在于后者可以返回一个函数?
我正在研究这个http://learnyouahaskell.com 的函子部分,有些解释有点不清楚。
Map 和 fmap 在以下方面的行为相同:
let exMap = map (+1) [1..5]
let exFMap = fmap (+1) [1..5]
什么是 fmap 返回函数的好例子?
【问题讨论】:
不,区别在于fmap 适用于任何函子。例如:
readLine :: IO String -- read a line
fmap length readLine :: IO Int -- read a line and count its length
Just 4 :: Maybe Int
fmap (+10) (Just 4) :: Maybe Int -- apply (+10) underneath Just
-- returns (Just 14)
map 将a -> b 转换为函数[] a -> [] b(通常写为[a] -> [b])。
fmap 将a -> b 转换为函数f a -> f b,用于任何函子f,而不仅仅是f = []。上面的例子选择了f = IO和f = Maybe。
【讨论】:
a->b和f a,当f是函子时,你可以使用fmap得到f b。但是,如果您有f (a->b) 和f a,则无法获得f b:该函数被包裹在f 之下,并且无法使其与其参数交互。应用函子改为定义一个额外的操作<*> 允许这样做。
查看每个函数的类型签名会有所帮助,让我们从您正在查看的 map 函数开始:
map :: (a -> b) -> [a] -> [b]
如您所见,map 函数对列表进行操作。然而,从逻辑上讲,有许多数据结构可以被映射。以下是一些其他可能的地图:
map :: (a -> b) -> Map k a -> Map k b
map :: (a -> b) -> Maybe a -> Maybe b
map :: (a -> b) -> IO a -> IO b
这只是冰山一角,很多东西都可以映射!
在不支持类型类的语言中,这可能就是您所知道的世界。有很多 map 函数,您必须使用适当的模块类型对它们进行限定,以区分您实际指的是哪个 map。在 Haskell 中并非如此!
现在让我们看看fmap:
fmap :: Functor f => (a -> b) -> f a -> f b
这个函数的形式与上面显示的完全相同,但适用于任何函子。
Functor 是这样定义的:
class Functor f where
fmap :: (a -> b) -> f a -> f b
(<$) :: a -> f b -> f a
(<$) = fmap . const
希望这表明仿函数只是支持被映射的东西。
因此fmap 是通用的,而map 是特定的。
【讨论】:
fmap超过Text或ByteString,因为它们不是函子。
fmap 比 map 更通用 - 实际上对于列表没有区别 - 这里有 map == fmap。
让我们从fmap的定义开始
class Functor f where
fmap :: (a -> b) -> (f a -> f b)
这实质上是说你可以把一个简单的函数变成一个函数,将容器转换成形状相同但元素不同的容器。
instance Functor [] where
fmap = map
instance Functor Maybe where
fmap _ Nothing = Nothing
fmap f (Just something) = Just (f something)
还有很多,我认为几乎每个具有单一类型参数的容器都可以是函子
作为练习,您可以尝试为
定义一个实例data Tree a = Tree a [a]
fmap的用法见@chi的回答。
【讨论】: