我正在研究Haskell,并试图理解如何将currying概念应用于函数。我理解currying本质上是一种带有多个参数的函数并将函数应用于一个参数的方法,返回一个应用于第二个函数的函数,依此类推......而不会失去任何表达性。我正在研究的一个教程问:
"写一个2 * (\x y -> x*y)2 3
"的咖喱版本
我希望有人可以帮我告诉我如何解决这个问题。提前致谢
编辑:回应两位评论者,我可以看到识别
(\x y -> x*y) :: Num a => a -> a -> a
...是我的第一步。在功能性编程方面我的学习曲线相当缓慢(也是新的SO海报,所以我打破了任何礼仪的借口)......我的下一步是什么?
编辑2:@Mikhail,我看到uncurry
应用于lambda表达式的类型会形成某种形式(给定uncurry :: (a -> b -> c) -> (a,b) -> c
)
Num a => (a,a) -> a
答案 0 :(得分:5)
您对currying的基本理解是正确的。具体来说,它是关于转换一个将其参数作为元组的函数,例如
add :: (Int, Int) -> Int
add (x, y) = x + y
进入一个一次获取一个参数的函数:
add' :: Int -> Int -> Int
add' x y = x + y
此方案允许您将现有的函数置于部分应用程序,即将其应用于某些但不是所有参数。例如,我们可以
succ :: Int -> Int
succ = add' 1
我们将add'
应用于它的第一个参数并产生一个仍然需要剩余参数的函数。
逆转换称为uncurrying,并将一个将其参数“逐个”的函数转换为一个函数,该函数将其参数“all once”作为元组。
两个转换都可以由高阶函数族捕获。也就是说,对于二进制函数,有
curry :: ((a, b) -> c) -> (a -> b -> c)
curry f = \x y -> f (x, y)
uncurry :: (a -> b -> c) -> ((a, b) -> c)
uncurry f = \(x, y) -> f x y
对于三元函数,有
curry3 :: ((a, b, c) -> d) -> (a -> b -> c -> d)
curry3 f = \x y z -> f (x, y, z)
uncurry3 :: (a -> b -> c -> d) -> ((a, b, c) -> d)
uncurry3 f = \(x, y, z) -> f x y z
等等。
现在让我们来看看你的例子:
2 * (\x y -> x * y) 2 3
在这里,您将文字2
乘以函数(\x y -> x * y)
的应用结果,该函数将其两个参数x
和y
相乘。正如您所看到的,此函数已经“逐个”获取其参数。因此,它已经咖喱了。所以,如果他们要求写一个这个表达式的咖喱版本,那么你的教程意味着什么呢?我们可以做的是通过使乘法函数将“一次性全部”变为参数来编写一个 uncurried 版本:(\(x, y) -> x * y)
。然后我们得到
2 * (\(x, y) -> x * y) (2, 3)
现在请注意,可以将(\(x, y) -> x * y)
写为uncurry (*)
,这会给我们
2 * uncurry (*) (2, 3)
如果我们也发现(*)
的第一个申请(或实际申请,复数;-)),我们会产生
uncurry (*) (2, uncurry (*) (2, 3))
我怀疑这是否是你的教程中练习背后的意图,但我希望这能为你提供一些关于讨论和解决问题的见解。