Scala中两种卷曲方式; 每个用例是什么用的?

dav*_*000 81 coding-style scala currying

我正在讨论Scala样式指南中的多个参数列表.我已经认识到,有两种方式讨好,我想知道用例是什么:

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)_
Run Code Online (Sandbox Code Playgroud)

风格指南错误地暗示这些是相同的,当它们显然不是时.该指南试图说明创建的curried函数,并且,虽然第二种形式不是"by-the-book"currying,但它仍然非常类似于第一种形式(尽管可以说更容易使用,因为你不需要的_)

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

ret*_*nym 130

多参数列表方法

对于类型推断

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

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

List("").foldLeft(0)(_ + _.length)
Run Code Online (Sandbox Code Playgroud)

如果是这样写的:

def foldLeft[B](z: B, op: (B, A) => B): B
Run Code Online (Sandbox Code Playgroud)

人们必须提供更明确的类型:

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

适用于流畅的API

多参数节方法的另一个用途是创建看起来像语言构造的API.调用者可以使用大括号而不是括号.

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

loop(2) {
   println("hello!")
}
Run Code Online (Sandbox Code Playgroud)

将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)
Run Code Online (Sandbox Code Playgroud)

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

您的第二个示例是混合:一个返回函数的参数节方法.

多阶段计算

咖喱功能还有用吗?这是一种始终出现的模式:

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);
Run Code Online (Sandbox Code Playgroud)

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

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

丑陋!我们纠结了无关紧要的问题 - 计算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))
Run Code Online (Sandbox Code Playgroud)

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

def v(t:Double): Double => Double = {
   val ft = f(t)
   (k: Double) => g(ft, k)       
}
Run Code Online (Sandbox Code Playgroud)

但是如果我们尝试对具有多个参数部分的方法执行相同的操作,我们就会陷入困境:

def v(t: Double)(k: Double): Double = {
                ^
                `-- Can't insert computation here!
}
Run Code Online (Sandbox Code Playgroud)

  • 很棒的答案; 希望我有更多的赞成而不仅仅是一个.我要消化并申请风格指南; 如果我成功了,这就是所选答案...... (3认同)

Lan*_*dei 16

你只能咖喱功能,而不是方法.add是一种方法,因此您需要_强制将其转换为函数.add2返回一个函数,所以这_不仅是不必要的,而且在这里没有任何意义.

考虑到不同的方法和功能(例如从JVM的角度来看),Scala做了很好的工作,模糊了它们之间的界限,并在大多数情况下做了"正确的事",但是有一点不同,有时你只需要了解它.


dde*_*any 5

我认为它有助于把握的差异,如果我补充一点,与def add(a: Int)(b: Int): Int你非常简单,只是定义一个方法有2个参数,只有这两个参数分为两个参数列表(见的是,在其他意见的后果).事实上,该方法就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.就像add(5)(6)只转换到add(5, 6)JVM级别一样,不会Function创建任何对象.另一方面,add2(5)(6)将首先创建一个Function包含的对象,5然后调用apply(6)它.