我试图了解fmap fmap
如何应用于诸如(*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
和(*)
都是多态函数,盲目使用它们会导致代码非常混乱(甚至可能不正确)。我之前曾回答过类似的问题:
What is happening when I compose * with + in Haskell?
在这种情况下,我认为查看值的类型可以帮助您弄清楚问题出在哪里以及如何解决问题。让我们从fmap
的类型签名开始:
fmap :: Functor f => (a -> b) -> f a -> f b
|______| |________|
| |
domain codomain
fmap
的类型签名很容易理解。它将功能从a
到b
提升到函子的上下文中,无论函子可能是什么(例如list,也许是一个,等等)。单词“域”和“共域”分别仅表示“输入”和“输出”。无论如何,让我们看看将
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 := g x -> g y
。另外,添加了Functor g
约束。这为我们提供了fmap fmap
的类型签名:fmap fmap :: (Functor f, Functor g) => f (x -> y) -> f (g x -> g y)
那么,
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
)内部查找,并将f
内部的函数提升到另一个函子g
的上下文中。到目前为止,一切都很好。所以有什么问题?问题是当您将
fmap fmap
应用于(*3)
时。这是愚蠢和危险的,例如酒后驾驶。让我告诉你为什么它如此愚蠢又危险。看一下(*3)
的类型签名:(*3) :: Num a => a -> a
将
fmap fmap
应用于(*3)
时,函子f
专用于(->) r
(即函数)。函数是有效的函子。 fmap
的(->) r
函数只是函数组成。因此,专门化的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 (*)
,则不会出现任何错误,原因与上文所述相同。关于function - fmap fmap如何应用于函数(作为参数)?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/28825354/