Haskell 哈斯克尔:翻转一个咖喱美元操作符

Haskell 哈斯克尔:翻转一个咖喱美元操作符,haskell,Haskell,假设我定义这个函数: f = ($ 5) g = flip f 然后我可以应用它: > f (\x -> x ^ 2) 25 其类型为: :t f f :: (Integer -> b) -> b 这很有意义,它获取一个函数作为参数,并返回应用于整数5的函数 现在我定义这个函数: f = ($ 5) g = flip f 我认为这是没有意义的,因为f是单个参数的函数 但是,检查其类型: :t g g :: b -> (Integer -> b -&

假设我定义这个函数:

f = ($ 5)
g = flip f
然后我可以应用它:

> f (\x -> x ^ 2)
25
其类型为:

:t f
f :: (Integer -> b) -> b
这很有意义,它获取一个函数作为参数,并返回应用于
整数
5
的函数

现在我定义这个函数:

f = ($ 5)
g = flip f
我认为这是没有意义的,因为
f
是单个参数的函数

但是,检查其类型:

:t g
g :: b -> (Integer -> b -> c) -> c
所以现在
g
是两个参数的函数

将其应用于某些值:

> g [2, 4, 6] (\x y -> x:y)
[5,2,4,6]
这是怎么回事?
flip($5)
真正的意思是什么

遵循以下类型:

($ 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
所以

这相当于您看到的类型(尽管我使用了
Int
而不是
Integer
来保存键入)

这意味着,
($5)
的类型在传递到
flip
时会被专门化,因此它需要两个参数的函数。使用类似于
($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)

函数
flip
翻转参数的顺序,因此所有参数都相等:

f (\x y -> x:y) [2, 4, 6]
[5,2,4,6]

[5,2,4,6]

[5,2,4,6]


这种混淆源于多态函数“参数数量”的松散概念。例如,这样说很有诱惑力

f :: (Integer -> b) -> b
有一个参数(一个函数)。然而,更精确的说法是,
f
是一个至少有一个参数的函数。这是因为由于多态性,类型变量
b
可以用任何类型替换,从而导致

f :: (Integer -> String) -> String
f :: (Integer -> Double) -> 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
。。。也许许多是一个更好的答案

f :: (Integer -> (String -> Double)) -> (String -> Double)
> id (+) 3 4
7
> id id id id id (+) 3 4
7