Wiele metod listy parametrów
Dla wnioskowania o typie
Metody z wieloma sekcjami parametrów mogą służyć do wspomagania wnioskowania o typie lokalnym, używając parametrów w pierwszej sekcji do wywnioskowania argumentów typu, które zapewnią oczekiwany typ argumentu w kolejnej sekcji. foldLeft
w bibliotece standardowej jest to kanoniczny przykład.
def foldLeft[B](z: B)(op: (B, A) => B): B
List("").foldLeft(0)(_ + _.length)
Gdyby to było zapisane jako:
def foldLeft[B](z: B, op: (B, A) => B): B
Należałoby podać bardziej wyraźne typy:
List("").foldLeft(0, (b: Int, a: String) => a + b.length)
List("").foldLeft[Int](0, _ + _.length)
Dla płynnego API
Innym zastosowaniem metod sekcji z wieloma parametrami jest utworzenie interfejsu API, który wygląda jak konstrukcja językowa. Osoba wywołująca może użyć nawiasów klamrowych zamiast nawiasów.
def loop[A](n: Int)(body: => A): Unit = (0 until n) foreach (n => body)
loop(2) {
println("hello!")
}
Zastosowanie list N argumentów do metody z sekcjami parametrów M, gdzie N <M, można przekonwertować na funkcję jawnie za pomocą _
lub niejawnie z oczekiwanym typem FunctionN[..]
. Jest to funkcja bezpieczeństwa, zapoznaj się z uwagami o zmianach dla Scala 2.0, w Referencjach Scala, aby zapoznać się z tłem.
Funkcje Curried
Funkcje curried (lub po prostu funkcje, które zwracają funkcje) można łatwiej zastosować do list N argumentów.
val f = (a: Int) => (b: Int) => (c: Int) => a + b + c
val g = f(1)(2)
Ta niewielka wygoda czasami się opłaca. Zauważ, że funkcje nie mogą być parametryczne, więc w niektórych przypadkach wymagana jest metoda.
Twój drugi przykład to hybryda: jednoparametrowa metoda sekcji, która zwraca funkcję.
Obliczenia wieloetapowe
Gdzie jeszcze przydatne są funkcje curry? Oto wzór, który pojawia się cały czas:
def v(t: Double, k: Double): Double = {
val ft = f(t)
g(ft, k)
}
v(1, 1); v(1, 2);
Jak możemy podzielić się wynikiem f(t)
? Typowym rozwiązaniem jest zapewnienie zwektoryzowanej wersji v
:
def v(t: Double, ks: Seq[Double]: Seq[Double] = {
val ft = f(t)
ks map {k => g(ft, k)}
}
Brzydki! Poplątaliśmy niepowiązane problemy - obliczanie g(f(t), k)
i mapowanie w sekwencji 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))
Moglibyśmy również użyć metody, która zwraca funkcję. W tym przypadku jest nieco bardziej czytelny:
def v(t:Double): Double => Double = {
val ft = f(t)
(k: Double) => g(ft, k)
}
Ale jeśli spróbujemy zrobić to samo z metodą z wieloma sekcjami parametrów, utkniemy:
def v(t: Double)(k: Double): Double = {
^
`-- Can't insert computation here!
}
def loop[A](n: Int)(body: => A): Unit = (0 until n) foreach (n => body)
val f: (a: Int) => (b: Int) => (c: Int) = a + b + c
Możesz curry tylko funkcji, a nie metod.
add
jest metodą, więc musisz_
wymusić jej konwersję na funkcję.add2
zwraca funkcję, więc_
jest nie tylko niepotrzebna, ale nie ma tu sensu.Biorąc pod uwagę, jak różne są metody i funkcje (np. Z punktu widzenia JVM), Scala całkiem nieźle radzi sobie z zatarciem granicy między nimi i w większości przypadków robi „The Right Thing”, ale jest różnica, a czasami wystarczy wiedzieć o tym.
źródło
Myślę, że pomocne jest uchwycenie różnic, jeśli dodam, że
def add(a: Int)(b: Int): Int
po prostu zdefiniuj metodę z dwoma parametrami, tylko te dwa parametry są zgrupowane na dwóch listach parametrów (zobacz konsekwencje tego w innych komentarzach). W rzeczywistości ta metoda jest takint add(int a, int a)
samo ważna jak Java (nie Scala!). Kiedy piszeszadd(5)_
, jest to po prostu literał funkcji, krótsza forma{ b: Int => add(1)(b) }
. Z drugiej strony, wraz zadd2(a: Int) = { b: Int => a + b }
tobą definiujesz metodę, która ma tylko jeden parametr, a dla Javy będzie nimscala.Function add2(int a)
. Kiedy piszeszadd2(1)
w Scali, jest to zwykłe wywołanie metody (w przeciwieństwie do literału funkcji).Zauważ również, że
add
ma (potencjalnie) mniej narzutu niżadd2
ma, jeśli natychmiast podasz wszystkie parametry. Tak jakadd(5)(6)
po prostu przekłada sięadd(5, 6)
na poziomie JVM, żadenFunction
obiekt nie jest tworzony. Z drugiej stronyadd2(5)(6)
najpierw utworzyFunction
obiekt, który otacza5
, a następnie wywołaapply(6)
to.źródło