多参数列表和每个列表中的多个参数在Scala中有什么区别?

86

在Scala中,可以像这样编写(柯里化的?)函数

def curriedFunc(arg1: Int) (arg2: String) = { ... }

这个curriedFunc函数的两个参数列表定义和单一参数列表中有多个参数的函数有什么区别:

def curriedFunc(arg1: Int, arg2: String) = { ... }

从数学的角度来看,这是 (curriedFunc(x))(y)curriedFunc(x,y),但我可以写成 def sum(x)(y) = x + y,同样的效果可以通过写成def sum2(x, y) = x + y 实现。

我只知道一个区别- 部分应用函数。但对我来说两种方式是等效的。

还有其他的区别吗?

4个回答

88

严格来说,这不是一个柯里化函数,而是一个带有多个参数列表的方法,尽管它看起来像一个函数。

正如您所说,多个参数列表允许该方法在部分应用函数的位置使用。(对于我使用的通常很傻的例子感到抱歉)

object NonCurr {
  def tabulate[A](n: Int, fun: Int => A) = IndexedSeq.tabulate(n)(fun)
}

NonCurr.tabulate[Double](10, _)            // not possible
val x = IndexedSeq.tabulate[Double](10) _  // possible. x is Function1 now
x(math.exp(_))                             // complete the application

另一个好处是,你可以使用花括号代替括号,如果第二个参数列表只包含一个函数或thunk,则看起来很漂亮。例如:

NonCurr.tabulate(10, { i => val j = util.Random.nextInt(i + 1); i - i % 2 })

对比

IndexedSeq.tabulate(10) { i =>
  val j = util.Random.nextInt(i + 1)
  i - i % 2
}

或者对于 thunk 的处理方式:
IndexedSeq.fill(10) {
  println("debug: operating the random number generator")
  util.Random.nextInt(99)
}

另一个优点是,您可以引用先前参数列表的参数来定义默认参数值(尽管您也可以说在单一列表中无法完成该操作 :))

// again I'm not very creative with the example, so forgive me
def doSomething(f: java.io.File)(modDate: Long = f.lastModified) = ???

最后,有三个应用程序回答了相关帖子为什么Scala提供多个参数列表和每个列表中的多个参数?。我会将它们复制到这里,但功劳归于Knut Arne Vedaa、Kevin Wright和extempore。
第一个:您可以拥有多个var args:
def foo(as: Int*)(bs: Int*)(cs: Int*) = as.sum * bs.sum * cs.sum

...这在单个参数列表中是不可能的。

其次,它有助于类型推断:

def foo[T](a: T, b: T)(op: (T,T) => T) = op(a, b)
foo(1, 2){_ + _}   // compiler can infer the type of the op function

def foo2[T](a: T, b: T, op: (T,T) => T) = op(a, b)
foo2(1, 2, _ + _)  // compiler too stupid, unfortunately

最后,这是你可以拥有隐式和非隐式参数的唯一方式,因为 implicit 是整个参数列表的修饰符。
def gaga [A](x: A)(implicit mf: Manifest[A]) = ???   // ok
def gaga2[A](x: A, implicit mf: Manifest[A]) = ???   // not possible

2
由于这是最受欢迎的答案,我认为问题的标题不再与其答案相对应。我认为标题应该更改为“为什么Scala提供多个参数列表和每个列表多个参数?”,即它已经由示例与https://dev59.com/S2445IYBdhLWcg3w0dZD合并。 - Jacek Laskowski

42

0__的回答非常好,但有一个区别没有提到:默认参数。一个参数列表中的参数可以在计算另一个参数列表中的默认值时使用,但不能在同一个参数列表中使用。

例如:

def f(x: Int, y: Int = x * 2) = x + y // not valid
def g(x: Int)(y: Int = x * 2) = x + y // valid

选取这个简单的例子是为了更好地理解。这使得默认参数变得更加有用。谢谢! - Mike McFarland
好的例子,只是我花了五分钟才弄清如何调用它: g(1)() 返回 3。g(1)(2) 返回 5。 - Sapience

20

这正是重点,柯里化和非柯里化形式是等效的!正如其他人指出的那样,根据情况,其中一个形式更方便使用,这是优选其中一种而非另一种的唯一原因。

重要的是要理解,即使 Scala 没有用于声明柯里化函数的特殊语法,你仍然可以构造它们;一旦你有了返回函数的能力,这只是数学上的必然性。

为了证明这一点,想象一下def foo(a)(b)(c) = {...} 语法不存在。那么你仍然可以通过以下方式实现完全相同的效果:def foo(a) = (b) => (c) => {...}

就像 Scala 中的许多功能一样,这只是一种用于完成可能仍可通过稍微冗长的方式达成的方便语法。


4
两种形式是同构的。主要区别在于,柯里化函数更容易部分应用,而非柯里化函数的语法略微更好,至少在Scala中是这样的。

2
之前不是说过这些例子不是柯里化函数吗?我理解柯里化函数只有一个参数,可能会返回一个带有单个参数的函数,以此类推,直到有一个包含所有参数的主体。我错了吗? - Jacek Laskowski

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接