【问题标题】:Difference between currying and higher-order functions柯里化和高阶函数之间的区别
【发布时间】:2013-09-17 07:25:16
【问题描述】:

Scala编程(控制抽象)我看到这两个例子效果一样:

1。高阶函数

def withPrintWriter(file: File, op: PrintWriter => Unit) {
  val writer = new PrintWriter(file)
  try {
    op(writer)
  } finally {
    writer.close()
  }
}

2。柯里化函数

def withPrintWriter(file: File)(op: PrintWriter => Unit) {
  val writer = new PrintWriter(file)
  try {
    op(writer)
  } finally {
    writer.close()
  }
}

它们之间有什么区别?我们能否始终以两种方式获得相同的结果?

【问题讨论】:

标签: scala


【解决方案1】:

高阶函数柯里化函数的概念通常以正交方式使用。 高阶函数只是一个接受一个函数作为参数或返回一个函数作为结果的函数,它可能会也可能会不要被咖喱。在一般用法中,提到高阶函数的人通常是在谈论将另一个函数作为参数的函数。

另一方面,curried 函数 是一个 返回函数作为其结果的函数。完全柯里化函数是一个单参数函数,它要么返回普通结果,要么返回完全柯里化函数。请注意,柯里化函数必然是高阶函数,因为它返回一个函数作为其结果。

因此,您的第二个示例是返回高阶函数的柯里化函数示例。这是另一个不将函数作为参数的柯里化函数示例,以各种 (nearly equivalent) 方式表示:

def plus(a: Int)(b:Int) = a + b
def plus(a: Int) = (b: Int) => a + b
val plus = (a: Int) => (b: Int) => a + b

【讨论】:

  • 非常简洁。
【解决方案2】:

高阶函数是将函数作为参数或返回函数或两者兼有的函数。

def f(g: Int => Int) = g(_: Int) + 23

scala> f(_ + 45)
res1: Int => Int = <function1>

scala> res1(4)
res2: Int = 72

这是一个高阶函数,它接受一个函数作为参数并返回另一个函数。如您所见,高阶函数是柯里化的先决条件。 curry 函数如下所示:

def curry[A,B,C](f: (A,B) => C) = (a: A) => (b: B) => f(a,b)

scala> curry((a: Int, b: Int) => a+b)
res3: Int => (Int => Int) = <function1>

scala> res3(3)
res4: Int => Int = <function1>

scala> res4(3)
res5: Int = 6

所以回答你的问题:它们是两个不同的概念,其中一个(高阶函数)是另一个(currying)的先决条件。

【讨论】:

    【解决方案3】:

    在语义上,我可以想到一个 curried 函数和一个非 curried 函数之间的区别。对于非咖喱版本,当您调用withPrintWriter 时,这是一个单一的方法调用。对于 curried 版本,它实际上将是两个方法调用。可以这样想:

    withPrintWriter.apply(file).apply(op)
    

    除此之外,我认为很多人在这种情况下使用柯里化来获得风格。在这里使用 currying 使它看起来更像是一个语言特性,而不是一个自定义函数调用,因为你可以像这样使用它:

    withPrintWriter(file){ op =>
       ...
    }
    

    以这种方式使用它是试图从语言本身模拟一些控制结构的痛点,但同样,这只是一种风格,它确实伴随着额外的方法调用的开销。

    您可以以几乎相同的方式使用非咖喱版本,但它看起来不那么干净:

    withPrintWriter(file, { op =>
       ...
    })
    

    编辑 @drexin 在他的回答中提出了一个很好的观点,我在这里值得一提。当你想到方法的柯里化版本的签名时,真的是:

    Function1[File, Function1[PrintWriter, Unit]]
    

    【讨论】:

    • 这并不完全正确。 curried 方法被编译为字节码级别的常用方法,并且只有部分应用程序会导致创建实际的函数对象。如果你完全应用它,它将是一个单一的方法调用。
    • @drexin,感谢您提供的信息。我不知道。所以我想对于这个例子,它归结为样式,除非你打算部分应用它,然后用剩下的一个 arg 完全应用它。
    • 实际上这只是风格问题,因为您可以部分应用任何方法,也可以将方法转换为柯里化函数。更多信息请参见我的帖子:stackoverflow.com/questions/12660852/…
    【解决方案4】:

    它们大多相同,但在类型推断方面存在差异。 Scala 无法推断单个方法调用的参数之间的类型,但它能够推断多个参数列表的类型。

    考虑:

    def foo1[T](x : T, y : T => T) = y(x)
    def foo2[T](x : T)(y : T => T) = y(x)
    
    foo1(1, t => t + 1) //does not compile with 'missing parameter type'
    foo2(1)(t => t + 1) //compiles
    

    你可以在这个答案中看到一些额外的信息:Multiple parameter closure argument type not inferred

    【讨论】:

      【解决方案5】:

      严格来说,你给出的例子并不是真正的柯里化,它只是有多个参数列表。碰巧的是,Scala 函数的多参数列表在许多情况下看起来很像柯里化函数。但是,当你调用一个多参数列表函数但没有填写一个或多个参数列表时,它实际上是一个部分应用的例子,而不是柯里化。调用一个包含所有参数的多参数列表只是一个函数调用,而不是每个参数列表一个。

      有两个用例可以使用多个参数列表函数。第一个是隐式参数的情况,因为所有隐式参数都必须在它们自己的参数列表中,与任何显式参数分开。第二个用例是接受其他函数作为参数的函数,因为如果作为函数的参数在它自己的参数列表中,你可以去掉括号而只使用大括号,使函数调用看起来像某种控制结构.

      除此之外,差异纯粹是装饰性的。

      【讨论】:

        猜你喜欢
        • 2013-09-14
        • 2016-06-17
        • 1970-01-01
        • 2012-04-25
        • 1970-01-01
        • 1970-01-01
        • 2016-01-01
        • 1970-01-01
        • 2021-02-07
        相关资源
        最近更新 更多