Function fmap fmap如何应用于函数(作为参数)?
我试图理解Function fmap fmap如何应用于函数(作为参数)?,function,haskell,types,functor,function-composition,Function,Haskell,Types,Functor,Function Composition,我试图理解fmap-fmap如何应用于say(*3)这样的函数 fmap-fmap的类型: (fmap fmap):: (Functor f1, Functor f) => f (a -> b) -> f (f1 a -> f1 b) fmap :: Functor f => (a -> b) -> f a -> f b |______| |________|
fmap-fmap
如何应用于say(*3)
这样的函数
fmap-fmap
的类型:
(fmap fmap):: (Functor f1, Functor f) => f (a -> b) -> f (f1 a -> f1 b)
fmap :: Functor f => (a -> b) -> f a -> f b
|______| |________|
| |
domain codomain
fmap :: Functor f => (a -> b) -> f a -> f b
|______|
|
fmap :: Functor g => (x -> y) -> g x -> g y
|______| |________|
| |
a -> b
fmap fmap :: (Functor f, Functor g) => f (x -> y) -> f (g x -> g y)
(*3)
的类型:
这意味着签名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))
*Main>:t(测试(*3))
:1:8:
没有因使用“*”而产生的(Num(a0->b0))实例
在“test”的第一个参数中,即“(*3)”
在表达式中:(test(*3))
为什么会发生这种情况?当你不知道自己在做什么时,多态性是危险的。
fmap
和(*)
都是多态函数,盲目使用它们会导致代码非常混乱(甚至可能不正确)。我以前也回答过类似的问题:
在这种情况下,我相信查看值的类型可以帮助您找出哪里出了问题,以及如何纠正问题。让我们从fmap
的类型签名开始:
(fmap fmap):: (Functor f1, Functor f) => f (a -> b) -> f (f1 a -> f1 b)
fmap :: Functor f => (a -> b) -> f a -> f b
|______| |________|
| |
domain codomain
fmap :: Functor f => (a -> b) -> f a -> f b
|______|
|
fmap :: Functor g => (x -> y) -> g x -> g y
|______| |________|
| |
a -> b
fmap fmap :: (Functor f, Functor g) => f (x -> y) -> f (g x -> g y)
fmap
的类型签名很容易理解。它将函数从a
提升到b
到一个函子的上下文中,不管该函子是什么(例如,列表,可能,或者,等等)
“域”和“编码域”分别表示“输入”和“输出”。无论如何,让我们看看当我们将fmap
应用到fmap
时会发生什么:
(fmap fmap):: (Functor f1, Functor f) => f (a -> b) -> f (f1 a -> f1 b)
fmap :: Functor f => (a -> b) -> f a -> f b
|______| |________|
| |
domain codomain
fmap :: Functor f => (a -> b) -> f a -> f b
|______|
|
fmap :: Functor g => (x -> y) -> g x -> g y
|______| |________|
| |
a -> b
fmap fmap :: (Functor f, Functor g) => f (x -> y) -> f (g x -> g y)
如您所见,a:=x->y
和b:=gx->gy
。此外,还添加了函子g
约束。这为我们提供了fmap-fmap
的类型签名:
(fmap fmap):: (Functor f1, Functor f) => f (a -> b) -> f (f1 a -> f1 b)
fmap :: Functor f => (a -> b) -> f a -> f b
|______| |________|
| |
domain codomain
fmap :: Functor f => (a -> b) -> f a -> f b
|______|
|
fmap :: Functor g => (x -> y) -> g x -> g y
|______| |________|
| |
a -> b
fmap fmap :: (Functor f, Functor g) => f (x -> y) -> f (g x -> g y)
那么,fmap-fmap
做什么呢?第一个fmap
将第二个fmap
提升到函子f
的上下文中。假设f
是可能。因此,关于专业化:
fmap fmap :: Functor g => Maybe (x -> y) -> Maybe (g x -> g y)
fmap fmap :: Maybe (x -> y) -> Maybe ([x] -> [y])
因此,fmap-fmap
必须应用于值,该值可能包含一个函数。fmap-fmap
的作用是将值中的函数提升到另一个functorg
的上下文中。假设g
是[]
。因此,关于专业化:
fmap fmap :: Functor g => Maybe (x -> y) -> Maybe (g x -> g y)
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
应用于(*3)
时。这是愚蠢和危险的,就像喝酒和开车一样。让我告诉你为什么这是愚蠢和危险的。查看(*3)
的类型签名:
当您将fmap-fmap
应用于(*3)
时,函子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 . (*) :: (Num a, Functor g) => a -> g a -> g a
test (*3) :: Num (a -> b) => Bool
让我告诉你为什么使用值是错误的:
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
test :: Functor f => f (a -> b) -> Bool
因此,r:=x:=y:=a
。这将为您提供类型签名:
fmap . (*3) :: (Num (x -> y), Functor g) => (x -> y) -> g x -> g y
fmap . (*) :: (Num a, Functor g) => a -> g a -> g a
test (*3) :: Num (a -> b) => Bool
当您看到以下值时,这就更有意义了:
fmap . (*)
= \x -> fmap (x *)
因此,fmap-fmap(*)3
只是fmap(3*)
最后,您的test
函数也有同样的问题:
(.) fmap :: Functor g => (r -> x -> y) -> r -> g x -> g y
|___________|
|
(*) :: Num a => a -> a -> a
| | |
r -> x -> y
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
。因此,我们得到类型签名:
fmap . (*3) :: (Num (x -> y), Functor g) => (x -> y) -> g x -> g y
fmap . (*) :: (Num a, Functor g) => a -> g a -> g a
test (*3) :: Num (a -> b) => Bool
由于输出类型中既不出现a
也不出现b
,因此必须立即解决约束Num(a->b)
。如果输出类型中出现了a
或b
,则可以对它们进行专门化,并可以选择Num(a->b)
的不同实例。但是,由于它们不出现在输出类型中,编译器必须决定立即选择Num(a->b)
的哪个实例;由于Num(a->b)
是一个愚蠢的约束,没有任何实例,编译器会抛出一个错误
如果您尝试test(*)
,那么您将不会得到任何错误,原因与我上面提到的相同。numa=>a->a
和f(x->y)
对齐不好,您将得到f~(->)a
和a~x->y
,因此Num(x->y)
。更有趣的可能是fmap($[1,2,3])$fmap-fmap$fmap(+)$Just 10
,它返回的仅为[11,12,13]
。一个更有用的Functor
组合器可能是fmap-fmap
,它允许您通过两个不同的Functor
列出一个函数:(.:)=fm