方法可能有多個參數清單。
範例
以下是 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 無法推斷函數 _ + _
的類型,因為它仍在推斷 A
和 B
。透過將參數 op
移到它自己的參數清單,A
和 B
會在第一個參數清單中推斷。這些推斷的類型會提供給第二個參數清單,而 _ + _
會符合推斷的類型 (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 標準函式庫的 curried
和 uncurried
方法的風險,它們根本不涉及多個參數清單。
無論如何,多個參數清單和柯里化之間肯定有相似之處。儘管它們在定義位置不同,但呼叫位置可能仍然看起來相同,如下例所示
// 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