哈斯克尔:翻转一个咖喱美元算子

时间:2015-05-14 18:53:07

标签: haskell

假设我定义了这个函数:

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)到底意味着什么?

3 个答案:

答案 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) constconst :: 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]