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