【问题标题】:Uncurried functions非咖喱函数
【发布时间】:2013-04-08 01:15:42
【问题描述】:

我无法理解 curried 和 uncurried 函数。我在 Google 上试图为我提供定义的所有网站我都不清楚。

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

max 4 5(max 4) 5 相同

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

【问题讨论】:

标签: haskell currying


【解决方案1】:

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

一个haskell函数:

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

真正的意思是:一个函数接受一个参数,然后返回另一个接受一个参数的函数。这称为柯里化。

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

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

意思完全一样。

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

foo1 :: Int -> Int
foo1 = foo 1

由于 haskell 中的函数应用程序只是空格,大多数时候您可以假装 curried 函数是 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 reduction 可能会有所帮助。

【讨论】:

  • 我知道它接受一个参数并返回另一个接受另一个参数的函数,但它如何计算任何东西?所以在你的情况下,如果我有函数 foo a 它应该如何只用一个参数做任何事情?我想我需要对它的工作过程进行更详细的解释。
  • 看看 lambdas。这就是实际发生的事情。等一下,我正在编辑,因为它太小了
  • @user1831442 你来了
  • 嗯,你是说foo a b = a + b (foo 4) 5 只是插入4 所以我们得到4 + b?我从你的例子中得到的是bar 只是在插入东西。啊,currying 似乎是一个很容易理解的概念,为什么我很难理解它?!
  • 是的,就是这样!它基本上归结为替换。不要担心每个学习 Haskell 的人都会遇到这样的事情:)
【解决方案2】:

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

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

另一方面,如果max 是一个高阶函数的参数,编译器可能无法静态知道max 需要多少个参数。也许在一种情况下它需要三个,所以max 4 5 是一个部分应用程序,或者在另一种情况下它只需要一个并且max 4 生成一个应用5 的新函数。本文讨论了处理未知数静态情况的两种常用方法。

【讨论】:

    【解决方案3】:

    您可能已经得到答案,但重申一下:

    如果我们有

    add x y = x + y
    

    那么我们可以这样说:

    add = \ x y -> x + y
    add 3 = \ y -> 3 + y
    add 3 5 = 3 + 5 = 8
    

    您问“max 3 如何计算任何东西?”,答案是“它不能”。它只是为您提供了另一个功能。当你调用这个函数时,它可以做一些事情,但是在提供 all 参数之前,你不会“得到答案”。在那之前,你只需要获取函数。

    大多数时候,这只是一个有用的语法快捷方式。例如,你可以写

    uppercase :: String -> String
    uppercase = map toUpper
    

    不用说

    uppercase xs = map toUpper xs
    

    请注意,如果 map 有相反的参数,我们将无法做到这一点(您只能将 last 参数,而不是 _first),所以它考虑定义函数参数的顺序可能很重要。


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

    那我到底是什么意思?好吧,例如,QuickCheck 可以测试带有 任何 个参数的函数(前提是有一种方法可以为每个参数自动生成测试数据)。这是可能的,因为函数类型是柯里化的。每个函数要么返回另一个函数,要么返回一个结果。如果你把它想象成一个链表,你可以想象 QuickCheck 递归地迭代函数直到没有更多的参数。

    以下代码 sn-p 可能会或可能不会解释这个想法:

    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) 也是如此。

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

    【讨论】:

      【解决方案4】:

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

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-02
        • 2011-04-21
        • 2013-08-26
        相关资源
        最近更新 更多