[英]How does fmap fmap apply to functions (as arguments)?
我试图理解fmap fmap
如何应用于像say (*3)
这样的函数。
fmap fmap
的类型:
(fmap fmap):: (Functor f1, Functor f) => f (a -> b) -> f (f1 a -> f1 b)
类型(*3)
:
(*3) :: Num a => a -> a
这意味着签名a -> a
对应f (a -> b)
,对吧?
Prelude> :t (fmap fmap (*3))
(fmap fmap (*3)):: (Num (a -> b), Functor f) => (a -> b) -> f a -> f b
我尝试过创建一个简单的测试:
test :: (Functor f) => f (a -> b) -> Bool
test f = True
喂食(*3)
,但我得到:
*Main> :t (test (*3))
<interactive>:1:8:
No instance for (Num (a0 -> b0)) arising from a use of ‘*’
In the first argument of ‘test’, namely ‘(* 3)’
In the expression: (test (* 3))
为什么会这样?
当你不知道自己在做什么时,多态性很危险。 fmap
和(*)
都是多态函数,盲目地使用它们会导致非常混乱(也可能是错误的)代码。 我之前回答过类似的问题:
在这种情况下,我认为查看值的类型可以帮助您找出出错的地方以及如何纠正问题。 让我们从fmap
的类型签名fmap
:
fmap :: Functor f => (a -> b) -> f a -> f b
|______| |________|
| |
domain codomain
fmap
的类型签名很容易理解。 它升降机的函数从a
到b
为算符,不管它仿函数可以是的上下文(例如列表,也许,要么,等)。
单词“domain”和“codomain”分别仅表示“输入”和“输出”。 无论如何,让我们看到,当我们申请会发生什么fmap
到fmap
:
fmap :: Functor f => (a -> b) -> f a -> f b
|______|
|
fmap :: Functor g => (x -> y) -> g x -> g y
|______| |________|
| |
a -> b
如您所见, a := x -> y
和b := gx -> gy
。 此外,还添加了Functor g
约束。 这给了我们fmap fmap
的类型签名:
fmap fmap :: (Functor f, Functor g) => f (x -> y) -> f (g x -> g y)
那么, fmap fmap
做了什么? 第一个fmap
将第二个fmap
提升到了一个fmap
函数f
的上下文中。 让我们说f
是Maybe
。 因此,在专业化:
fmap fmap :: Functor g => Maybe (x -> y) -> Maybe (g x -> g y)
因此,必须将fmap fmap
应用于其中包含函数的Maybe
值。 fmap fmap
作用是将Maybe
值中的函数提升到另一个函子g
的上下文中。 假设g
是[]
。 因此,在专业化:
fmap fmap :: Maybe (x -> y) -> Maybe ([x] -> [y])
如果我们将fmap fmap
应用于Nothing
那么我们得到Nothing
。 但是,如果我们将它应用于Just (+1)
那么我们得到一个函数,它增加列表的每个数字,包含在Just
构造函数中(即我们得到Just (fmap (+1))
)。
但是, fmap fmap
更为通用。 它实际上做它,它看起来算符内f
(无论f
而定)和电梯里的功能(S) f
进入另一个函子上下文g
。
到现在为止还挺好。 所以有什么问题? 问题是当您将fmap fmap
应用于(*3)
。 这是愚蠢和危险的,如酒后驾车。 让我告诉你为什么它是愚蠢和危险的。 看一下(*3)
的类型签名:
(*3) :: Num a => a -> a
当您将fmap fmap
应用于(*3)
时, fmap fmap
函数f
专门用于(->) r
(即函数)。 函数是一个有效的函子。 (->) r
的fmap
函数只是函数组合。 因此,专业化的fmap fmap
类型是:
fmap fmap :: Functor g => (r -> x -> y) -> r -> g x -> g y
-- or
(.) fmap :: Functor g => (r -> x -> y) -> r -> g x -> g y
|___________|
|
(*3) :: Num a => a -> a
| |
| ------
| | |
r -> x -> y
你明白为什么这是愚蠢和危险的吗?
r -> x -> y
)的输入函数到一个只有一个参数的函数, (*3) :: Num a => a -> a
。 (*3)
的输出是多态的。 因此,编译器不会告诉您您正在做一些愚蠢的事情。 幸运的是,因为输出是有界的,你得到一个类型约束Num (x -> y)
,它应该表明你在某个地方出了问题。 计算出类型, r := a := x -> y
。 因此,我们得到以下类型签名:
fmap . (*3) :: (Num (x -> y), Functor g) => (x -> y) -> g x -> g y
让我告诉你使用值时出错的原因:
fmap . (*3)
= \x -> fmap (x * 3)
|_____|
|
+--> You are trying to lift a number into the context of a functor!
你真正想要做的是将fmap fmap
应用于(*)
,这是一个二元函数:
(.) fmap :: Functor g => (r -> x -> y) -> r -> g x -> g y
|___________|
|
(*) :: Num a => a -> a -> a
| | |
r -> x -> y
因此, r := x := y := a
。 这为您提供了类型签名:
fmap . (*) :: (Num a, Functor g) => a -> g a -> g a
当您看到值时,这更有意义:
fmap . (*)
= \x -> fmap (x *)
因此, fmap fmap (*) 3
只是fmap (3*)
。
最后,您的test
功能存在同样的问题:
test :: Functor f => f (a -> b) -> Bool
在专业化函子f
到(->) r
我们得到:
test :: (r -> a -> b) -> Bool
|___________|
|
(*3) :: Num x => x -> x
| |
| ------
| | |
r -> a -> b
因此, r := x := a -> b
。 因此我们获得了类型签名:
test (*3) :: Num (a -> b) => Bool
由于输出类型中既不出现a
也不出现b
,因此必须立即解析约束Num (a -> b)
。 如果a
或b
出现在输出类型中,则它们可以是专用的,并且可以选择不同的Num (a -> b)
实例。 但是,因为它们没有出现在输出类型中,所以编译器必须决定立即选择哪个Num (a -> b)
实例; 并且因为Num (a -> b)
是一个没有任何实例的愚蠢约束,编译器会抛出错误。
如果您尝试test (*)
那么您将不会收到任何错误,原因与上面提到的相同。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.