函数组合运算符(.)和fmap(<$>)的区别
Difference between function composition operator (.) and fmap (<$>)
目前正在通读 this article(顺便说一句,这非常棒)并且有一个非常简单的问题:
如果我将 (+3)
和 (+2)
等两个函数与 <$>
结合起来,它似乎给了我一个新函数,将传递给它的任何内容加 5。如果我对函数组合运算符做同样的事情,即 (+3) . (+2)
,它不会做同样的事情吗?如果这是真的,那么这两个运算符之间是否存在某种关系,使得它们在这种简单情况下做同样的事情?
这是一个聪明的问题吗?
要查找有关 Functor
函数实例的信息,请匹配类型以查找相关实例:
fmap :: (a -> b) -> f a -> f b
然后这里 a ~ Int
、b ~ Int
和 f ~ (->) Int
.
您可以看到 GHC here 附带的所有 Functor
个实例。 (->)
只是一个带有两个类型参数的中缀类型运算符。我们通常看到它应用为Int -> Int
,但这相当于(->) Int Int
。
有一个 Functor
实例用于(部分应用的)类型 (->) r
(对于任何类型 r::*
)。
查看 ((->) r)
instance for Functor
,我们看到 fmap = (.)
,因此 (+3) . (+2)
和 fmap (+3) (+2)
之间没有实际区别(与 (+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
并以 .
结束呢?我假设您了解 Functor 是什么,所以现在您必须了解 "functions" 是 Functor。怎么会?
> :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
不同,函数没有可以使用的构造函数。 Functor 实例应用于语法本身。我们可以从 ghci 的 :info 中看到语法箭头 ->
实际上有一个 functor.
的实例
当我们查看 +3 的类型时会发生什么?
> :t (+3)
(+3) :: Num a => a -> a
所以函数 (+3) 是一个接受 a 和 returns a 的 Functor。当我们在一个 Functor 上使用 fmap
并且它也返回一个 Functor 时,我们得到嵌套的 Functor:
> :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]]
我们实际上需要依赖它是一个 Monad a
,这样我们就可以使用一个绑定 >>=
.但是函数 (->)
是一个特例,因为我们知道每次我们在函数上使用 fmap
时,它总是会在函数的一侧给我们一个函数。除了 ->
之外的任何其他 Functor 都不能这样说。因此,我们可以确保始终在函数上连接 fmap
。
因此任何f <$> g == f . g
编辑:一个简短的旁注,如果你这样做 fmap (+) (+0)
你最终会在一个函数中得到一个函数。在这种情况下,实际上需要 monadic bind (>>=
) 来连接函数:
> :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]
目前正在通读 this article(顺便说一句,这非常棒)并且有一个非常简单的问题:
如果我将 (+3)
和 (+2)
等两个函数与 <$>
结合起来,它似乎给了我一个新函数,将传递给它的任何内容加 5。如果我对函数组合运算符做同样的事情,即 (+3) . (+2)
,它不会做同样的事情吗?如果这是真的,那么这两个运算符之间是否存在某种关系,使得它们在这种简单情况下做同样的事情?
这是一个聪明的问题吗?
要查找有关 Functor
函数实例的信息,请匹配类型以查找相关实例:
fmap :: (a -> b) -> f a -> f b
然后这里 a ~ Int
、b ~ Int
和 f ~ (->) Int
.
您可以看到 GHC here 附带的所有 Functor
个实例。 (->)
只是一个带有两个类型参数的中缀类型运算符。我们通常看到它应用为Int -> Int
,但这相当于(->) Int Int
。
有一个 Functor
实例用于(部分应用的)类型 (->) r
(对于任何类型 r::*
)。
查看 ((->) r)
instance for Functor
,我们看到 fmap = (.)
,因此 (+3) . (+2)
和 fmap (+3) (+2)
之间没有实际区别(与 (+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
并以 .
结束呢?我假设您了解 Functor 是什么,所以现在您必须了解 "functions" 是 Functor。怎么会?
> :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
不同,函数没有可以使用的构造函数。 Functor 实例应用于语法本身。我们可以从 ghci 的 :info 中看到语法箭头 ->
实际上有一个 functor.
当我们查看 +3 的类型时会发生什么?
> :t (+3)
(+3) :: Num a => a -> a
所以函数 (+3) 是一个接受 a 和 returns a 的 Functor。当我们在一个 Functor 上使用 fmap
并且它也返回一个 Functor 时,我们得到嵌套的 Functor:
> :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]]
我们实际上需要依赖它是一个 Monad a
,这样我们就可以使用一个绑定 >>=
.但是函数 (->)
是一个特例,因为我们知道每次我们在函数上使用 fmap
时,它总是会在函数的一侧给我们一个函数。除了 ->
之外的任何其他 Functor 都不能这样说。因此,我们可以确保始终在函数上连接 fmap
。
因此任何f <$> g == f . g
编辑:一个简短的旁注,如果你这样做 fmap (+) (+0)
你最终会在一个函数中得到一个函数。在这种情况下,实际上需要 monadic bind (>>=
) 来连接函数:
> :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]