函数组合运算符 (.) 和 fmap (<$>) 之间的区别



目前正在阅读这篇文章(顺便说一句,这是非常出色的),并有一个非常简单的问题:

如果我将 (+3)(+2) 这两个函数与 <$> 结合起来,它似乎给了我一个新函数,为传递给它的任何函数加 5。如果我对函数组合运算符做同样的事情,即 (+3) . (+2),它不会做同样的事情吗?如果这是真的,那么这两个运算符之间是否存在关系,以至于他们在这个简单的情况下做同样的事情?

这甚至是一个聪明的问题吗?

函数 fmap<$> 都具有相同的类型:

> :t fmap
fmap :: Functor f => (a -> b) -> f a -> f b
> :t (<$>)
(<$>) :: Functor f => (a -> b) -> f a -> f b

虽然函数.

> :t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c

那么我们怎么可能在函数上使用fmap并最终得到.呢?我假设你了解函子是什么,所以现在你必须明白"函数"是函子。怎么会这样?

> :i (->)
data (->) a b   -- Defined in `GHC.Prim'
instance Monad ((->) r) -- Defined in `GHC.Base'
instance Functor ((->) r) -- Defined in `GHC.Base'
instance Applicative ((->) a) -- Defined in `Control.Applicative'

Just[]Left不同,函数没有可以使用的构造函数。函子实例应用于语法本身。我们可以从 ghci 中的 :info 中看到,句法箭头实际上->有一个函子实例。

当我们查看 +3 的类型时会发生什么?

> :t (+3)
(+3) :: Num a => a -> a

因此,函数 (+3) 是一个接受 a 并返回 a 的函子。当我们在函子上使用fmap并且这也给了我们一个函子时,我们得到了嵌套的函子:

> :t fmap Just (Just 3)
fmap Just (Just 3) :: Num a => Maybe (Maybe a)
> :t fmap (replicate 5) [1,2,3]
fmap (replicate 5) [1,2,3] :: Num a => [[a]]

同样,当我们fmap应用于两个函数时,我们会在函数中得到一个函数。唯一的区别是它们融合在一起:

> :t (fmap (+3) (+2))
(fmap (+3) (+2)) :: Num a => a -> a

为什么这不会导致类型 (->) (->) a a ?我们必须记住,fmap的第一个参数是一个函数(a -> b)不一定是 Functor。因此,当我们做fmap g (Just 5)我们可以进行任何转变。但是每当我们对一个函数执行fmap时,我们知道它总是会在函数内部产生一个函数。

因此fmap (+3) (+2)计算结果如下:x -> (x' -> x' + 3) (x + 2) 。这是一种非常迂回的写法(+3) . (+2)

> :t (fmap (+3) (+2))
(fmap (+3) (+2)) :: Num a => a -> a
> :t ((.) (+3) (+2))
((.) (+3) (+2)) :: Num a => a -> a

通常要解决(Maybe (Maybe a))[[a]]的concat问题,我们实际上需要依靠它是一个Monad a,以便我们可以使用绑定>>=。但是函数(->)是一个特例,因为我们知道每次我们在函数上使用fmap时,它总是会在函数的一侧为我们提供一个函数。除了->之外,任何其他函子都不能这样说。因此,我们可以确保始终在函数上连接fmap

因此,任何f <$> g == f . g

编辑:快速旁注,如果你这样做fmap (+) (+0)你最终会在函数中得到一个函数。在这种情况下,实际上需要一元绑定(>>=)来连接函数:

> :t fmap (+) (+0)
fmap (+) (+0) :: Num a => a -> a -> a
> :t (+0) >>= (+)
(+0) >>= (+) :: Num b => b -> b
> let bindfunc = (+0) >>= (+)
> bindfunc 5
10

这与我们在执行[1,2] >>= replicate 5时得到的行为并不完全不同:

> [1,2] >>= replicate 5
[1,1,1,1,1,2,2,2,2,2]

若要查找有关函数的Functor实例的信息,请匹配类型以查找相关实例:

fmap :: (a -> b) -> f a -> f b

然后这里是a ~ Intb ~ Intf ~ (->) Int

您可以在此处查看 GHC 附带的所有Functor实例。 (->) 只是一个具有两个类型参数的中缀类型运算符。我们通常将其视为Int -> Int,但这相当于(->) Int Int。(部分应用的)类型(->) r(对于任何类型r::*)都有一个Functor实例。

查看Functor ((->) r)实例,我们看到fmap = (.),所以(+3) . (+2)fmap (+3) (+2)之间没有实际区别(与(+3) <$> (+2)相同)。

最新更新