两种方式在Scala中curl; 每个用例有什么用途?

我正在围绕我维护的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)_ 

风格指南错误地暗示这些是相同的,当他们明显不是。 该指南试图就创build的咖喱函数提出一个观点,虽然第二种forms不是“通过书本”的咖喱,但它仍然非常类似于第一种forms(尽pipe可以说使用起来更容易,因为您不需要_

从那些使用这些forms的人来说,什么时候使用这种forms的共识是什么?

多参数列表方法

对于types推断

具有多个参数部分的方法可用于辅助局部types推断,方法是使用第一部分中的参数来推断将在后续部分中为参数提供预期types的​​types参数。 标准库中的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 

人们必须提供更明确的types:

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

stream利的API

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

 def loop[A](n: Int)(body: => A): Unit = (0 until n) foreach (n => body) loop(2) { println("hello!") } 

将N参数列表应用于具有M个参数部分的方法,其中N <M,可以用_明确地转换为函数,或者隐式地转换为具有期望typesFunctionN[..]FunctionN[..] 。 这是一个安全function,请参阅Scala参考中有关Scala 2.0的更改说明。

咖喱function

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

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

这个小小的便利有时候是值得的。 请注意,函数不能是types参数,所以在某些情况下需要一个方法。

你的第二个例子是一个混合:一个参数部分的方法,返回一个函数。

多阶段计算

curried函数还有哪些用处? 这里总是出现一个模式:

 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) ? 一个常见的解决scheme是提供一个vector化版本的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! } 

你可以只咖喱function,而不是方法。 add是一个方法,所以你需要_强制将其转换为一个函数。 add2返回一个函数,所以_不仅是不必要的,而且在这里是没有意义的。

考虑到不同的方法和function(例如从JVM的angular度来看),Scala在模糊他们之间的界限和做大多数情况下的“正确的事情”方面做得相当不错,但是有一个区别,有时候你只需要了解它。

我认为这有助于掌握差异,如果我添加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.Function add2(int a) 。 当你在Scala中编写add2(1) ,它只是一个普通的方法调用(而不是函数文本)。

另外请注意,如果您立即提供所有参数,则add (具有)可能比add2less的开销。 像add(5)(6)只是转换为在JVM级别上add(5, 6) ,则不会创buildFunction对象。 另一方面, add2(5)(6)将首先创build一个包含5Function对象,然后调用apply(6)