Scala中的两种计算方式;每个都有什么用例?


83

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

样式指南错误地暗示了它们是相同的,但显然不是相同的。该指南试图说明已创建的咖喱函数,尽管第二种形式不是“按书预定”,但它仍然与第一种形式非常相似(尽管可以说它更易于使用,因为您不需要的_

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

Answers:


137

多参数列表方法

对于类型推断

具有多个参数部分的方法可用于协助局部类型推断,方法是使用第一部分中的参数来推断类型实参,该实参将为后续部分中的实参提供期望的类型。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,可以使用_或的预期类型隐式转换为函数FunctionN[..]。这是一项安全功能,请参阅《 Scala参考》中有关Scala 2.0的更改说明。

咖喱函数

咖喱函数(或简单地,返回函数的函数)更容易应用于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!
}

3
好的答案;希望我的投票比只有更多。我将消化并将其应用于样式指南;如果我成功的话,这是选择的答案……
davetron5000

1
您可能需要将循环示例更正为:def loop[A](n: Int)(body: => A): Unit = (0 until n) foreach (n => body)
Omer van Kloeten

无法编译:val f: (a: Int) => (b: Int) => (c: Int) = a + b + c
nilskp 2012年

“将N个参数列表应用于具有M个参数部分的方法,其中N <M,可以使用_显式地转换为函数,或者使用FunctionN [..]的预期类型隐式地转换为函数。” <br/>应该不是FunctionX [..],其中X = MN吗?
stackoverflower

“这不会编译:val f:(a:Int)=>(b:Int)=>(c:Int)= a + b + c”我不认为“ f:(a:Int)= >(b:Int)=>(c:Int)“是正确的语法。可能追溯指“ f:Int => Int => Int => Int”。因为=>是正确的关联,所以实际上是“ f:Int =>(Int =>(Int => Int))”。因此,f(1)(2)的类型为Int => Int(即f的类型中最里面的位)
stackoverflower

16

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

考虑到不同的方法和功能如何(例如从JVM的角度看),斯卡拉做了不错的工作,模糊了他们之间的线,在大多数情况下,做“正确的事”,但有有差别,有时你只需要知道这一点。


这很有意义,那么您怎么称呼def add(a:Int)(b:Int)形式?描述def和def add(a:Int,b:Int)之间的区别的术语/短语是什么?
davetron5000

@ davetron5000第一个是带有多个参数列表的方法,第二个是带有一个参数列表的方法。
Jesper

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编写代码时,它只是一个简单的方法调用(与函数文字相反)。

另请注意,addadd2立即提供所有参数相比,开销(可能)要少。就像在JVM级别上add(5)(6)转换为一样add(5, 6),不会Function创建任何对象。另一方面,add2(5)(6)将首先创建一个Function包含的对象,5然后对其进行调用apply(6)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.