不安全的功能
我无法理解curried和uncurried函数。我谷歌的所有网站都试图为我提供一个定义,但对我而言并不清楚。不安全的功能
在一个例子中,我发现他们说
max 4 5
相同(max 4) 5
但我不明白他们在做什么。当max需要2个参数时,如何获得(max 4)
功能?我完全失去了。
Haskell的诀窍是函数只有一个参数。这看起来非常疯狂,但它确实有效。
一个Haskell函数:
foo :: Int -> Int -> Int
foo a b = a + b
真正含义是:函数,它接受在1个参数,然后返回另一个函数,它带有一个参数。这叫做咖喱。
所以用这个,我们真的可以写这个函数的定义是这样的:
foo :: Int -> (Int -> Int) --In math speak: right associative
,并意味着完全一样的东西。
这实际上是超级有用的,因为我们现在可以编写简洁的代码,如:
foo1 :: Int -> Int
foo1 = foo 1
由于在Haskell功能的应用仅仅是空白,大部分的时间你可以假装咖喱功能uncurried(采取更多比一个参数还要返回结果)。
如果你确实真的需要uncurried函数:使用元组。
uncFoo :: (Int, Int) -> Int
uncFoo (a, b) = a + b
编辑
行,所以要了解怎么回事与部分应用程序考虑bar
bar a b c = [a, b, c]
的事情是,编译器将desugar你刚才输入到这样
lambda表达式bar = \a ->
\b ->
\c ->
[a, b, c]
这利用了闭包(每个内部函数都可以“记住”以前的参数。
所以当我们说bar 1
,编译器去,并看着bar
,看到最外层的λ,并将其应用于给
bar 1 = \b ->
\c ->
[1, b, c]
如果说bar 1 2
bar 1 2 = \c ->
[1, 2, c]
如果我是什么意思时,我说“应用”是模糊的,那么它可能有助于知道我真的是从lambda演算中得到beta reduction。
根据您的背景,您可能会发现本文有启发性:How to Make a Fast Curry: Push/Enter vs Eval Apply。虽然多参数函数可以理解为绑定单个参数并返回另一个函数的函数:max = (\a -> (\b -> if a > b then a else b))
,但实际实现效率更高。
如果编译器静态地知道max
需要两个参数,编译器将始终通过推入堆栈(或寄存器中)的两个参数来转换max 4 5
,然后调用max
。这与C编译器如何翻译max(4, 5)
基本相同。另一方面,如果例如max
是更高阶函数的参数,则编译器可能不会静态知道max
需要多少个参数。也许在一个例子中,它需要三个,所以max 4 5
是一个部分应用程序,或者也许在另一个它只需要一个和max 4
生成一个新的功能,应用5
。本文讨论了处理不确定静态的情况的两种常见方法。
涉及到你自己的例子......
假设您想要一个函数,给出了最大的4和功能参数。您可以实现这样的:
max4 :: Integer -> Integer
max4 x = max 4 x
max 4
做什么只是返回动态创建功能max4
。
你可能有已在您的回答,只是重申:
如果我们有
add x y = x + y
那么我们可以说以下内容:
add = \ x y -> x + y
add 3 = \ y -> 3 + y
add 3 5 = 3 + 5 = 8
你问“怎么会max 3
计算什么?“,答案是”它不能“。它只是给你另一个函数。这个函数可以在你调用它的时候做一些事情,但是你不会“得到一个答案”,直到所有的参数都被提供。在此之前,你只需要获取函数。
大多数时候,这仅仅是一个有用的语法捷径。例如,你可以写
uppercase :: String -> String
uppercase = map toUpper
,而不必说
uppercase xs = map toUpper xs
注意,如果map
有其他方式的争论,我们就无法做到这一点(你只能咖喱了最后论点,而不是_first),所以重要的是要考虑你定义你的函数的论点的顺序。
我说“大部分时间”,因为这不仅仅是语法糖。在语言中有几个地方可以使用不同数量的参数来处理函数多态性,因为它是柯里化的。每个函数都会返回一个答案或另一个函数。如果你认为它像一个链表(它包含下一项数据或列表结束标记),你可以看到这是如何递归处理函数的。
那么究竟发生了什么我的意思了?嗯,比如说,快速检查可以测试功能与任何数量的参数(提供有一种方法来自动生成每个参数的测试数据)。这是可能的,因为功能类型是咖喱。每个函数都会返回另一个函数或结果。如果你想像链表一样,你可以想象QuickCheck递归迭代函数,直到没有更多的参数被留下。
下面的代码片段可能会或可能无法解释的想法:
class Arbitrary a where
autogenerate :: RandomGenerator -> a
instance Arbitrary Int
instance Arbitrary Char
...
class Testable t where
test t :: RandomGenerator -> Bool
instance Testable Bool where
test rnd b = b
instance (Arbitrary a, Testable t) => Testable (a -> t) where
test rnd f = test $ f (autogenerate rnd)
如果我们有一个功能foo :: Int -> Int -> Bool
,那么这是Testable
。为什么?那么,Bool
是可测试的,因此这样的Int -> Bool
,因此这样是Int -> (Int -> Bool)
。
相反,元组的每个尺寸是不同的大小,所以你必须写为每个和元组的每一个单独的大小的函数(或实例)。你不能递归地处理元组,因为它们没有递归结构。
参见[什么是哗众取宠的优势在哪里?(http://programmers.stackexchange.com/q/185585/61231)。 – 2013-03-21 05:48:00
我想这个问题,我的答案可能有帮助:http://*.com/questions/8148253/how-are-functions-curried/8148957 – Ben 2013-03-21 05:54:03