什么是 ((+) .(+))在哈斯克尔



在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 => ay具有类型 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]]