我正在讨论我维护的Scala风格指南中的 Multiple Parameter Lists 。我已经意识到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)_
风格指南错误地暗示这些是相同的,当它们显然不是时。该指南试图对创建的curried函数提出一个观点,虽然第二种形式不是“by-the-book”curry,但它仍然非常类似于第一种形式(尽管可以说更容易使用,因为你不需要_
)
从那些使用这些表格的人那里,关于何时使用一种表格而不是另一种表格的共识是什么?
答案 0 :(得分:129)
具有多个参数部分的方法可用于辅助本地类型推断,方法是使用第一部分中的参数来推断将在后续部分中为参数提供预期类型的类型参数。标准库中的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。调用者可以使用大括号而不是括号。
def loop[A](n: Int)(body: => A): Unit = (0 until n) foreach (n => body)
loop(2) {
println("hello!")
}
将N个参数列表应用于具有M个参数部分的方法,其中N <1。 M,可以使用_
显式转换为函数,或隐式转换为预期类型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!
}
答案 1 :(得分:16)
你只能咖喱函数而不是方法。 add
是一种方法,因此您需要_
强制将其转换为函数。 add2
返回一个函数,因此_
不仅是不必要的,而且在这里没有任何意义。
考虑到不同的方法和功能(例如从JVM的角度来看),Scala做了很好的工作,模糊了它们之间的界限并在大多数情况下做了“正确的事”,但是强烈的>差异,有时你只需要知道它。
答案 2 :(得分:5)
我认为如果我用def add(a: Int)(b: Int): Int
添加它就有助于掌握差异你几乎只用两个参数定义一个方法,只有这两个参数被分组为两个参数列表(看看其他评论中的后果)。实际上,就Java(而不是Scala!)而言,该方法仅为int add(int a, int a)
。当你写add(5)_
时,这只是一个函数文字,一个较短的{ b: Int => add(1)(b) }
形式。另一方面,使用add2(a: Int) = { b: Int => a + b }
定义一个只有一个参数的方法,而对于Java,它将是scala.Function add2(int a)
。当您在Scala中编写add2(1)
时,它只是一个简单的方法调用(而不是函数文字)。
另请注意,如果您立即提供所有参数,add
的开销(可能)会比add2
少。与add(5)(6)
仅在JVM级别转换为add(5, 6)
一样,不会创建Function
个对象。另一方面,add2(5)(6)
将首先创建一个Function
对象,其中包含5
,然后在其上调用apply(6)
。