3

我无法理解 curried 和 uncurried 函数。我谷歌试图为我提供定义的所有网站对我来说都不清楚。

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

max 4 5是相同的(max 4) 5

但我不明白他们在做什么。(max 4)当 max 需要 2 个参数时,你怎么能有一个函数?我完全迷路了。

4

4 回答 4

14

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 中的函数应用程序只是空白,大多数时候你可以假装 curried 函数是 uncurried (接受多个参数并只返回一个结果)。

如果你真的真的真的需要 uncurried 函数:使用元组。

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

编辑

好的,所以要了解部分应用程序发生了什么,请考虑bar

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

问题是,编译器会像这样对你刚刚输入 lambda 的内容进行脱糖

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可能会有所帮助。

于 2013-03-21T04:28:30.757 回答
4

根据您的背景,您可能会发现这篇论文很有启发性:如何制作快速咖喱:Push/Enter vs Eval Apply。虽然确实可以将多参数函数理解为绑定单个参数并返回另一个函数的函数:max = (\a -> (\b -> if a > b then a else b)),但实际实现要高效得多。

如果编译器静态知道max需要两个参数,编译器将始终max 4 5通过将两个参数压入堆栈(或寄存器)然后调用max. 这与 C 编译器的翻译方式基本相同max(4, 5)

另一方面,如果 instancemax是高阶函数的参数,编译器可能不知道静态需要多少参数max。也许在一种情况下它需要三个,max 4 5部分应用程序也是如此,或者在另一种情况下它只需要一个并max 4生成一个5应用到的新函数。这篇论文讨论了两种常见的方法来处理数量不是静态已知的情况。

于 2013-03-21T05:17:38.497 回答
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 可以使用任意数量的参数测试函数(前提是有一种方法可以为每个参数自动生成测试数据)。这是可能的,因为函数类型是柯里化的。每个函数要么返回另一个函数,要么返回一个结果。如果你把它想象成一个链表,你可以想象 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)

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

于 2013-03-21T08:11:16.937 回答
0

与您自己的示例有关...

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

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

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

于 2013-03-21T06:50:43.753 回答