在ghci中,
:t ((+).(+))
> ((+).(+)) :: (Num (a -> a), Num a) => a -> (a -> a) -> a -> a
但这是什么东西?任何人都可以给我一个使用它的例子吗?
一个复合 2 个函数,每个函数接受 2 个参数?例如,(map.map) :: (a -> b) -> [[a]] -> [[b]]
如何工作?
(^.^)
(-.-)
(+.+)
(忍不住从中做鬼脸。PS:我以为意思是告诉编译器你今天的感受(
Num (a -> a)
(或例如 Eq (a -> a)
(基本上是没有任何意义的代码的指示符1,但编译器仍然推导出一个(无意义的(类型签名。通常,当您忘记将函数应用于某些参数时,它就会出现。在这种情况下,显然(+)
需要一个"纯数字"参数才能成为一个"简单函数",你可以将另一个这样的函数后组合到该函数中。
但是,(a -> a)
肯定是一种有效的函数类型,您也可以传递,只是不是数字。例如,map . (+)
是一个非常好的组合:
Prelude> :t map . (+)
map . (+) :: Num b => b -> [b] -> [b]
Prelude> zipWith (map . (+)) [10,20,30] [[1,2],[3,4]]
[[11,12],[23,24]]
因为map
实际上期望函数作为其第一个参数。同样地
Prelude> zipWith (map . map) [(+10),(+20),(+30)] [[[1,2],[3,4]],[[5,6]]]
[[[11,12],[13,14]],[[25,26]]]
在这里,右map
采用一个简单的函数(如数字增量(并返回相应的列表映射函数。然后将该函数馈送到左侧map
从而生成映射嵌套列表的函数。
1实际上,您可以通过定义来强制它有意义
instance (Num a) => Num (b -> a) where
fromInteger x = const $ fromInteger x
f + g = x -> f x + g x
就个人而言,我不喜欢这个。例如,当大多数人期望乘法12
时,let a = 3 in 4 a
会产生4
。
那行不通。正如ghci告诉你的那样,你应该有一个Num (a -> a)
的实例才能使用该函数,但a -> a
显然不是一个数字。
这是因为(+)
假设得到两个数值参数,但是对于您编写的合成,您给了它一个部分应用的函数,即计算类型签名中提到的a -> a
。
通常,在组合接受多个参数的函数时,首先部分应用它们,以便将它们减少为只接受一个参数的函数,例如 应用于3
(+1) . (*2)
将导致(3 * 2) + 1 = 7
>f . f
对于二进制函数f
是有意义的;它完全取决于f
的签名。关键是内部f
对其第一个参数的部分应用必须提供一些对外部f
的有效输入。
例如,使用 map :: (a -> b) -> [a] -> [b]
,我们可以手动统一map . map
:
map :: (a -> b) -> [a] -> [b]
map :: (c -> d) -> [c] -> [d]
. :: (e -> f) -> (f -> g) -> (e -> g)
e === a -> b
f === [a] -> [b]
=== c -> d
c === [a]
d === [b]
g === [c] -> [d] === [[a]] -> [[b]]
map . map :: e -> g
:: (a -> b) -> [[a]] -> [[b]]
因此,正如预期的那样,map . map
进行了转换a -> b
,并为我们提供了从列表列表a
到列表列表b
的转换。 我们可以通过手动应用(map . map) f ll
来检查这一点:
(map . map) f ll
= map (map f) ll
= map (l -> map f l) ll
但是如果我们尝试对 (+) :: Num a => a -> a -> a
做同样的事情,一切都会大错特错:
(+) :: Num a => a -> a -> a
(+) :: Num b => b -> b -> b
. :: (c -> d) -> (d -> e) -> (c -> e)
c === a
d === a -> a
=== b
e === b -> b === (a -> a) -> (a -> a)
(+) . (+) :: c -> e
:: (Num a, Num (a -> a)) => a -> (a -> a) -> (a -> a)
因此,内部+
的部分应用是给出一个转换a -> a
,然后外部+
试图将该转换添加到我们期望提供的另一个函数中。 由于添加转换没有意义,因此整体(+) . (+)
也没有意义。
g . f
表示先应用 f
,然后将g
应用于 f
的结果,在换句话说,它可以重写为
x -> g (f x)
因此
((+) . (+))
可以改写为
x -> (y -> (x +) + y)
根据(+)
的类型,在上面的lambda抽象中,x
需要具有类型 Num a => a
,y
具有类型 Num a => Num (a -> a)
,如推断的那样按ghci
(Num a, Num (a -> a)) => a -> (a -> a) -> a -> a
因此,如果我们a -> a
类型类 Num a
的实例,例如,这是实现这一目标的一种方法
{-# LANGUAGE FlexibleInstances #-}
instance (Num a) => Num ((->) a a) where
a + b = x -> a x + b x
a * b = x -> a x * b x
a - b = x -> a x - b x
negate a = x -> negate $ a x
abs a = x -> abs $ a x
signum a = x -> signum $ a x
fromInteger n = _x -> fromInteger n
我们可以像这样使用((+) . (+))
*Main> ((+) . (+)) 1 (+2) 3
9
因为((+) . (+))
等于
x -> y -> (x +) + y
这意味着((+) . (+)) 1 (+2) 3
等于
((1 + ) + (+ 2)) 3
根据(a -> a)
实例中(+)
的定义,((1+) +
(+2))
等于
x -> (1+x) + (x+2)
所以((1+) + (+2)) 3
等于 (1+3) + (3+2)
,即 9,由 ghci
给出。
map . map
类似,如其类型所示,由 ghci
给出:
(a -> b) -> [[a]] -> [[b]]
该函数的第一个参数应该是类型 a->b
的函数,第二个参数应该是类型 [[a]]
的嵌套列表,并且由函数 map . map
将第一个参数应用于每个元素的每个元素list 在其第二个参数中,返回类型为 [[b]]
的嵌套列表。为例
*Main> (map . map) (+1) [[1,2], [3,4,5]]
[[2,3],[4,5,6]]