84

我在我维护的 Scala 样式指南中讨论了多个参数列表。我开始意识到有两种方式currying,我想知道用例是什么:

def add(a:Int)(b:Int) = {a + b}
// Works
add(5)(6)
// Doesn't compile
val f = add(5)
// Works
val f = add(5)_
f(10) // yields 15

def add2(a:Int) = { b:Int => a + b }
// Works
add2(5)(6)
// Also works
val f = add2(5)
f(10) // Yields 15
// Doesn't compile
val f = add2(5)_

样式指南错误地暗示它们是相同的,而它们显然不是。该指南试图说明创建的柯里化函数,虽然第二种形式不是“按书本”柯里化,但它仍然与第一种形式非常相似(尽管可以说更容易使用,因为你不需要_) _

从那些使用这些表格的人那里,关于何时使用一种表格而不是另一种表格的共识是什么?

4

3 回答 3

139

多参数列表方法

对于类型推断

具有多个参数部分的方法可用于辅助本地类型推断,方法是使用第一部分中的参数来推断类型参数,该类型参数将为后续部分中的参数提供预期类型。foldLeft在标准库中就是典型的例子。

def foldLeft[B](z: B)(op: (B, A) => B): B

List("").foldLeft(0)(_ + _.length)

如果这是这样写的:

def foldLeft[B](z: B, op: (B, A) => B): B

必须提供更明确的类型:

List("").foldLeft(0, (b: Int, a: String) => a + b.length)
List("").foldLeft[Int](0, _ + _.length)

对于流畅的 API

多参数部分方法的另一个用途是创建一个看起来像语言结构的 API。调用者可以使用大括号代替圆括号。

def loop[A](n: Int)(body: => A): Unit = (0 until n) foreach (n => body)

loop(2) {
   println("hello!")
}

将 N 个参数列表应用于具有 M 个参数部分的方法,其中 N < M,可以显式转换为具有 a 的函数_,或隐式转换为具有预期类型的​​函数FunctionN[..]。这是一项安全功能,有关背景信息,请参阅 Scala 参考中的 Scala 2.0 更改说明。

咖喱函数

Curried 函数(或者简单地说,返回函数的函数)更容易应用于 N 个参数列表。

val f = (a: Int) => (b: Int) => (c: Int) => a + b + c
val g = f(1)(2)

这种小小的便利有时是值得的。请注意,函数不能是参数类型,因此在某些情况下需要方法。

您的第二个示例是混合:返回函数的单参数部分方法。

多阶段计算

柯里化函数还有什么用处?这是一个一直出现的模式:

def v(t: Double, k: Double): Double = {
   // expensive computation based only on t
   val ft = f(t)

   g(ft, k)
}

v(1, 1); v(1, 2);

我们如何分享结果f(t)?一个常见的解决方案是提供一个矢量化版本v

def v(t: Double, ks: Seq[Double]: Seq[Double] = {
   val ft = f(t)
   ks map {k => g(ft, k)}
}

丑陋的!我们已经纠缠了不相关的问题——计算g(f(t), k)和映射一系列ks.

val v = { (t: Double) =>
   val ft = f(t)
   (k: Double) => g(ft, k)       
}
val t = 1
val ks = Seq(1, 2)
val vs = ks map (v(t))

我们还可以使用返回函数的方法。在这种情况下,它更具可读性:

def v(t:Double): Double => Double = {
   val ft = f(t)
   (k: Double) => g(ft, k)       
}

但是如果我们尝试对具有多个参数部分的方法做同样的事情,我们就会卡住:

def v(t: Double)(k: Double): Double = {
                ^
                `-- Can't insert computation here!
}
于 2011-02-06T22:22:42.330 回答
16

您只能对函数进行柯里化,而不是方法。add是一种方法,因此您需要将_其强制转换为函数。add2返回一个函数,所以_这里不仅没有必要而且毫无意义。

考虑到方法和函数有多么不同(例如,从 JVM 的角度来看),Scala 在模糊它们之间的界限并在大多数情况下做“正确的事情”方面做得很好,但有区别,有时你只需要了解它。

于 2011-02-06T18:10:21.023 回答
5

def add(a: Int)(b: Int): Int我认为如果我与您一起添加几乎只是定义一个带有两个参数的方法,我认为这有助于掌握差异,只有这两个参数被分组到两个参数列表中(请参阅其他评论中的结果)。事实上,就int add(int a, int a)Java(不是 Scala!)而言,该方法就是如此。当您编写add(5)_时,这只是一个函数字面量,是 . 的较短形式{ b: Int => add(1)(b) }。另一方面,add2(a: Int) = { b: Int => a + b }定义一个只有一个参数的方法,对于 Java,它将是scala.Function add2(int a). 当您add2(1)在 Scala 中编写时,它只是一个普通的方法调用(与函数文字相反)。

另请注意,如果您立即提供所有参数,add则(可能)具有更少的开销。add2就像只是在 JVM 级别上add(5)(6)转换一样,不会创建任何对象。另一方面,将首先创建一个包含 的对象,然后调用它。add(5, 6)Functionadd2(5)(6)Function5apply(6)

于 2011-02-07T16:10:09.640 回答