2013-03-21 51 views
3

我无法理解curried和uncurried函数。我谷歌的所有网站都试图为我提供一个定义,但对我而言并不清楚。不安全的功能

在一个例子中,我发现他们说

max 4 5相同(max 4) 5

但我不明白他们在做什么。当max需要2个参数时,如何获得(max 4)功能?我完全失去了。

+1

参见[什么是哗众取宠的优势在哪里?(http://programmers.stackexchange.com/q/185585/61231)。 – 2013-03-21 05:48:00

+0

我想这个问题,我的答案可能有帮助:http://stackoverflow.com/questions/8148253/how-are-functions-curried/8148957 – Ben 2013-03-21 05:54:03

回答

13

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

+0

我明白,它需要一个参数,返回另一个函数,它接受另一种说法,但它是如何计算的?所以在你的情况下,如果我有'foo a'这个函数,它应该怎么做只有一个参数?我想我需要更详细的解释它的工作过程。 – dtgee 2013-03-21 04:52:08

+0

看看lambdas。这就是实际发生的情况。嗯,编辑因为这太小了 – jozefg 2013-03-21 04:52:55

+0

@ user1831442你在这里 – jozefg 2013-03-21 05:03:49

4

根据您的背景,您可能会发现本文有启发性: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。本文讨论了处理不确定静态的情况的两种常见方法。

0

涉及到你自己的例子......

假设您想要一个函数,给出了最大的4和功能参数。您可以实现这样的:

max4 :: Integer -> Integer 
max4 x = max 4 x 

max 4做什么只是返回动态创建功能max4

1

你可能有已在您的回答,只是重申:

如果我们有

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)

相反,元组的每个尺寸是不同的大小,所以你必须写为每个和元组的每一个单独的大小的函数(或实例)。你不能递归地处理元组,因为它们没有递归结构。