Scala 導覽

多個參數清單

語言

方法可能有多個參數清單。

範例

以下是 Scala 函式庫中 Iterable 特質中定義的範例

trait Iterable[A] {
  ...
  def foldLeft[B](z: B)(op: (B, A) => B): B
  ...
}
trait Iterable[A]:
  ...
  def foldLeft[B](z: B)(op: (B, A) => B): B
  ...

foldLeft 套用一個二參數函式 op 到一個初始值 z 和此函式庫中的所有元素,從左到右。以下是其用法範例。

從初始值 0 開始,foldLeft 在此套用函式 (m, n) => m + n 到清單中的每個元素和之前累積的值。

val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val res = numbers.foldLeft(0)((m, n) => m + n)
println(res) // 55

使用案例

建議的多個參數清單使用案例包括

驅動類型推論

在 Scala 中,類型推論一次處理一個參數清單。假設您有下列方法

def foldLeft1[A, B](as: List[A], b0: B, op: (B, A) => B) = ???

然後您想用下列方式呼叫它,但會發現它無法編譯

def notPossible = foldLeft1(numbers, 0, _ + _)

您必須用下列其中一種方式呼叫它

def firstWay = foldLeft1[Int, Int](numbers, 0, _ + _)
def secondWay = foldLeft1(numbers, 0, (a: Int, b: Int) => a + b)

這是因為 Scala 無法推斷函數 _ + _ 的類型,因為它仍在推斷 AB。透過將參數 op 移到它自己的參數清單,AB 會在第一個參數清單中推斷。這些推斷的類型會提供給第二個參數清單,而 _ + _ 會符合推斷的類型 (Int, Int) => Int

def foldLeft2[A, B](as: List[A], b0: B)(op: (B, A) => B) = ???
def possible = foldLeft2(numbers, 0)(_ + _)

這個定義不需要任何類型提示,且可以推斷它所有的類型參數。

隱含參數

若要僅指定某些參數為 implicit,它們必須放在它們自己的 implicit 參數清單中。

一個範例如下

def execute(arg: Int)(implicit ec: scala.concurrent.ExecutionContext) = ???
def execute(arg: Int)(using ec: scala.concurrent.ExecutionContext) = ???

部分應用

當以較少的參數清單呼叫方法時,這會產生一個函數,將遺失的參數清單作為其引數。這在形式上稱為 部分應用

例如,

val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val numberFunc = numbers.foldLeft(List[Int]()) _

val squares = numberFunc((xs, x) => xs :+ x*x)
println(squares) // List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100)

val cubes = numberFunc((xs, x) => xs :+ x*x*x)
println(cubes)  // List(1, 8, 27, 64, 125, 216, 343, 512, 729, 1000)

與「柯里化」的比較

有時你可能會看到一個有多個參數清單的方法稱為「柯里化」。

正如 維基百科上的柯里化文章 所述,

柯里化是一種將接受多個引數的函數轉換成一系列函數的技術,每個函數都接受單一引數

我們不鼓勵使用「柯里」這個詞來指稱 Scala 的多個參數清單,原因有兩個

1) 在 Scala 中,多個參數和多個參數清單是作為語言的一部分直接指定和實作的,而不是從單一參數函數衍生而來。

2) 有混淆 Scala 標準函式庫的 currieduncurried 方法的風險,它們根本不涉及多個參數清單。

無論如何,多個參數清單和柯里化之間肯定有相似之處。儘管它們在定義位置不同,但呼叫位置可能仍然看起來相同,如下例所示

// version with multiple parameter lists
def addMultiple(n1: Int)(n2: Int) = n1 + n2
// two different ways of arriving at a curried version instead
def add(n1: Int, n2: Int) = n1 + n2
val addCurried1 = (add _).curried
val addCurried2 = (n1: Int) => (n2: Int) => n1 + n2
// regardless, all three call sites are identical
addMultiple(3)(4)  // 7
addCurried1(3)(4)  // 7
addCurried2(3)(4)  // 7

此頁面的貢獻者