未完成的功能

时间:2013-03-21 04:22:58

标签: haskell currying

我无法理解咖喱和无故障的功能。我谷歌试图向我提供定义的所有网站都不清楚。

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

max 4 5(max 4) 5

相同

但我不明白他们在做什么。当max需要2个参数时,如何使用函数(max 4)?我完全迷失了。

4 个答案:

答案 0 :(得分:13)

Haskell的技巧是函数只接受一个参数。这看起来很疯狂,但确实有效。

haskell功能:

foo :: Int -> Int -> Int
foo a b = a + b

真的意思是:一个接受1个参数的函数,然后返回另一个接受一个参数的函数。这称为currying。

所以使用这个,我们可以像这样编写这个函数定义:

foo :: Int -> (Int -> Int) --In math speak: right associative

并且意思完全相同。

这实际上非常有用,因为我们现在可以编写简洁的代码,如:

foo1 :: Int -> Int
foo1 = foo 1

由于haskell中的函数应用程序只是空格,因此大多数情况下你可以假装curried函数不受约束(使用多个参数并返回结果)。

如果你真的真的需要未经证实的功能:使用元组。

uncFoo :: (Int, Int) -> Int
uncFoo (a, b) = a + b

修改

好的,以便了解部分应用程序正在进行的考虑bar

bar a b c = [a, b, c]

问题是,编译器会将你刚刚输入的内容类似于这样的

bar = \a ->
      \b ->
      \c ->
           [a, b, c]

这利用了闭包(每个内部函数都可以'记住'前面的参数。

所以当我们说bar 1时,编译器会查看bar并查看最外面的lambda,然后将其应用于

bar 1 = \b ->
        \c ->
             [1, b, c]

如果我们说bar 1 2

bar 1 2 = \c ->
                [1, 2, c]

如果我说“申请”的含义是模糊的,那么从lambda演算中得知我的确是beta reduction可能会有所帮助。

答案 1 :(得分: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的新函数。本文讨论了处理静态未知的情况的两种常用方法。

答案 2 :(得分: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的论点反过来,我们将无法做到这一点(你只能讨论 last 参数,而不是_first),因此,考虑定义函数参数的顺序非常重要。


我说“大部分时间”因为这不仅仅是语法糖。在语言中有几个地方可以处理具有不同数量的参数的函数多态因为currying。每个函数都返回一个答案或另一个函数。如果您将其视为链接列表(包含下一个数据项或列表结束标记),您可以看到它如何以递归方式处理函数。

那么我的意思是什么呢?好吧,例如,QuickCheck可以使用任何个参数测试函数(前提是有一种方法可以为每个参数自动生成测试数据)。这是可能的,因为函数类型是curry。每个函数都返回另一个函数或结果。如果您将其视为链表,可以想象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)也是如此。

相比之下,元组的每个大小都是不同的大小,因此您必须为每个元组大小编写单独的函数(或实例)。你不能递归地处理元组,因为它们没有递归结构。

答案 3 :(得分:0)

与你自己的例子有关...

假设您需要一个最大值为4的函数和函数参数。你可以像这样实现它:

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

max 4所做的只是返回动态创建的函数max4