12

查看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()
  }
}

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

4

5 回答 5

22

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

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

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

def plus(a: Int)(b:Int) = a + b
def plus(a: Int) = (b: Int) => a + b
val plus = (a: Int) => (b: Int) => a + b
于 2013-09-16T18:26:14.903 回答
7

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

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

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

scala> res1(4)
res2: Int = 72

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

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)的先决条件。

于 2013-09-16T14:11:24.163 回答
3

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

withPrintWriter.apply(file).apply(op)

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

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

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

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

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

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

Function1[File, Function1[PrintWriter, Unit]]
于 2013-09-16T12:51:27.183 回答
1

它们大多相同,但在类型推断方面存在差异。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

于 2013-09-16T15:00:00.783 回答
0

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

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

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

于 2013-09-16T23:25:16.857 回答