假设我定义了这个函数:
f = ($ 5)
然后我就可以申请了:
> f (\x -> x ^ 2)
25
其类型是:
:t f
f :: (Integer -> b) -> b
这是有道理的,它将函数作为参数,并返回应用于Integer
5
的此函数。
现在我定义了这个函数:
g = flip f
我希望这没有意义,因为f
是单个参数的函数。
但是,检查它的类型:
:t g
g :: b -> (Integer -> b -> c) -> c
所以现在g
是2个参数的函数!
将其应用于某些值:
> g [2, 4, 6] (\x y -> x:y)
[5,2,4,6]
这里发生了什么? flip ($ 5)
到底意味着什么?
答案 0 :(得分:9)
按照以下类型:
($ 5) :: (Int -> a) -> a
flip :: (x -> y -> z) -> y -> x -> z
但由于->
是正确关联的,因此x -> y -> z
类型等同于x -> (y -> z)
,所以
flip :: (x -> (y -> z)) -> y -> x -> z
($ 5) :: (Int -> a) -> a
所以x ~ (Int -> a)
和(y -> z) ~ a
,所以替换回来:
($ 5) :: (Int -> (y -> z)) -> (y -> z)
简化
($ 5) :: (Int -> y -> z) -> y -> z
所以
flip ($ 5) :: y -> (Int -> y -> z) -> z
这相当于您所看到的类型(虽然我使用Int
代替Integer
来保存输入。)
这就是说($ 5)
的类型在传递给flip
时变得专业化,因此它需要2个参数的函数。拥有($ 5) const
,const :: a -> b -> a
和($ 5) const :: b -> Int
等内容非常有效。所有($ 5)
正在做的是将5
作为 参数应用于函数,而不一定是函数的 参数。这是部分应用程序的示例,其中并非所有参数都提供给函数。这就是为什么你可以做map (subtract 1) [1, 2, 3]
之类的事情。
如何使用flip ($ 5)
的示例是:
> flip ($ 5) 2 (**)
25.0
> flip ($ 5) 1 (-)
4.0
> let f x y = (x, y)
> flip ($ 5) 1 f
(5, 1)
答案 1 :(得分:5)
混淆源于多态函数的“参数数量”的松散概念。例如,很容易说
f :: (Integer -> b) -> b
有一个参数(一个函数)。然而,更准确的说法是f
是至少一个参数的函数。这是因为类型变量b
可以用任何类型替换,这要归功于多态性,从而产生例如。
f :: (Integer -> String) -> String
f :: (Integer -> Double) -> Double
...
确实是带有一个参数的函数,但也到,例如
f :: (Integer -> (String -> Double)) -> (String -> Double)
其中b
已替换为功能类型String -> Double
。这种替换使得第二个参数以一种看似神奇的方式“出现”:f
可以采用第一个参数(二元函数Integer -> String -> Double
),然后是第二个参数(一个String
),在返回Double
之前。
请注意,对于某些类型变量... -> b
,当多态类型以b
结尾时,始终会出现此现象。
让我以琐事结束:“很多”论点如何具有身份函数id
?嗯,直觉上我会说一个,但让我检查......
> id (+) 3 4
7
> id id id id id (+) 3 4
7
...也许很多是一个更好的答案。
答案 2 :(得分:1)
函数flip
翻转参数的顺序,所以这些都是相同的:
f (\x y -> x:y) [2, 4, 6]
[5,2,4,6]
flip f [2, 4, 6] (\x y -> x:y)
[5,2,4,6]
g [2, 4, 6] (\x y -> x:y)
[5,2,4,6]