egd*_*try 4 haskell types function functor function-composition
我试图了解如何fmap fmap适用于 say 之类的函数(*3)。
的类型fmap fmap:
(fmap fmap):: (Functor f1, Functor f) => f (a -> b) -> f (f1 a -> f1 b)
Run Code Online (Sandbox Code Playgroud)
类型(*3):
(*3) :: Num a => a -> a
Run Code Online (Sandbox Code Playgroud)
这意味着签名a -> a对应于f (a -> b),对吗?
Prelude> :t (fmap fmap (*3))
(fmap fmap (*3)):: (Num (a -> b), Functor f) => (a -> b) -> f a -> f b
Run Code Online (Sandbox Code Playgroud)
我尝试创建一个简单的测试:
test :: (Functor f) => f (a -> b) -> Bool
test f = True
Run Code Online (Sandbox Code Playgroud)
并(*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))
Run Code Online (Sandbox Code Playgroud)
为什么会这样?
当您不知道自己在做什么时,多态性是危险的。这两个fmap和(*)是多态的功能和使用它们盲目地会导致非常混乱(也可能是不正确的)代码。我之前回答过一个类似的问题:
在这种情况下,我相信查看值的类型可以帮助您找出哪里出错以及如何纠正问题。让我们从 的类型签名开始fmap:
fmap :: Functor f => (a -> b) -> f a -> f b
|______| |________|
| |
domain codomain
Run Code Online (Sandbox Code Playgroud)
的类型签名fmap很容易理解。它将函数从ato 提升b到函子的上下文中,无论该函子可能是什么(例如列表,也许,或者,等等)。
“域”和“共域”这两个词分别表示“输入”和“输出”。无论如何,让我们看到,当我们申请会发生什么fmap到fmap:
fmap :: Functor f => (a -> b) -> f a -> f b
|______|
|
fmap :: Functor g => (x -> y) -> g x -> g y
|______| |________|
| |
a -> b
Run Code Online (Sandbox Code Playgroud)
如您所见,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)
Run Code Online (Sandbox Code Playgroud)
那么,有什么作用fmap fmap呢?第一个fmap将第二个提升fmap到函子的上下文中f。让我们说f是Maybe。因此,在专业方面:
fmap fmap :: Functor g => Maybe (x -> y) -> Maybe (g x -> g y)
Run Code Online (Sandbox Code Playgroud)
因此fmap fmap必须应用于Maybe其中包含函数的值。是什么fmap fmap做的是,它提升内部的函数Maybe值到另一个函子的情况下g。让我们说g是[]。因此,在专业方面:
fmap fmap :: Maybe (x -> y) -> Maybe ([x] -> [y])
Run Code Online (Sandbox Code Playgroud)
如果我们申请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
Run Code Online (Sandbox Code Playgroud)
当您申请fmap fmap时(*3),函子f专用于(->) r(即一个函数)。一个函数是一个有效的函子。fmapfor的函数(->) 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
Run Code Online (Sandbox Code Playgroud)
你明白为什么它是愚蠢和危险的吗?
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
Run Code Online (Sandbox Code Playgroud)
让我告诉你为什么使用值是错误的:
fmap . (*3)
= \x -> fmap (x * 3)
|_____|
|
+--> You are trying to lift a number into the context of a functor!
Run Code Online (Sandbox Code Playgroud)
你真正想做的是 apply fmap fmapto (*),它是一个二元函数:
(.) fmap :: Functor g => (r -> x -> y) -> r -> g x -> g y
|___________|
|
(*) :: Num a => a -> a -> a
| | |
r -> x -> y
Run Code Online (Sandbox Code Playgroud)
因此,r := x := y := a。这为您提供了类型签名:
fmap . (*) :: (Num a, Functor g) => a -> g a -> g a
Run Code Online (Sandbox Code Playgroud)
当您看到这些值时,这更有意义:
fmap . (*)
= \x -> fmap (x *)
Run Code Online (Sandbox Code Playgroud)
因此,fmap fmap (*) 3很简单fmap (3*)。
最后,你的test函数也有同样的问题:
test :: Functor f => f (a -> b) -> Bool
Run Code Online (Sandbox Code Playgroud)
在专门的函子上f,(->) r我们得到:
test :: (r -> a -> b) -> Bool
|___________|
|
(*3) :: Num x => x -> x
| |
| ------
| | |
r -> a -> b
Run Code Online (Sandbox Code Playgroud)
因此,r := x := a -> b。因此我们得到类型签名:
test (*3) :: Num (a -> b) => Bool
Run Code Online (Sandbox Code Playgroud)
由于既没有a也没有b出现在输出类型中,Num (a -> b)必须立即解决约束。如果a或b出现在输出类型中,那么它们可以被专门化并且Num (a -> b)可以选择不同的实例。但是,因为它们没有出现在输出类型中,所以编译器必须立即决定选择哪个实例Num (a -> b);因为Num (a -> b)是一个没有任何实例的愚蠢约束,编译器会抛出一个错误。
如果您尝试,test (*)则不会出现任何错误,原因与我上面提到的相同。