Scala Language
Funkcje częściowe
Szukaj…
Kompozycja
Funkcje częściowe są często używane do definiowania funkcji całkowitej w częściach:
sealed trait SuperType
case object A extends SuperType
case object B extends SuperType
case object C extends SuperType
val pfA: PartialFunction[SuperType, Int] = {
case A => 5
}
val pfB: PartialFunction[SuperType, Int] = {
case B => 10
}
val input: Seq[SuperType] = Seq(A, B, C)
input.map(pfA orElse pfB orElse {
case _ => 15
}) // Seq(5, 10, 15)
W tym zastosowaniu próbowane są funkcje częściowe w kolejności konkatenacji z metodą orElse
. Zazwyczaj dostępna jest końcowa funkcja częściowa, która pasuje do wszystkich pozostałych przypadków. Łącznie połączenie tych funkcji działa jako funkcja całkowita.
Ten wzorzec jest zwykle używany do oddzielenia problemów, w których funkcja może skutecznie działać jako dyspozytor dla różnych ścieżek kodu. Jest to powszechne na przykład w metodzie odbioru aktora Akka .
Użycie z `Collect`
Podczas gdy funkcje częściowe są często używane jako wygodna składnia dla funkcji całkowitych, włączając ostateczne dopasowanie symboli wieloznacznych ( case _
), w niektórych metodach ich stronniczość jest kluczowa. Jednym z bardzo powszechnych przykładów w idiomatycznej Scali jest metoda collect
zdefiniowana w bibliotece kolekcji Scala. W tym przypadku funkcje częściowe umożliwiają wspólne funkcje badania elementów kolekcji w celu mapowania i / lub filtrowania ich w jednej zwartej składni.
Przykład 1
Zakładając, że mamy funkcję pierwiastka kwadratowego zdefiniowaną jako funkcję częściową:
val sqRoot:PartialFunction[Double,Double] = { case n if n > 0 => math.sqrt(n) }
Możemy go wywołać za pomocą kombinatora collect
:
List(-1.1,2.2,3.3,0).collect(sqRoot)
skutecznie wykonując tę samą operację, co:
List(-1.1,2.2,3.3,0).filter(sqRoot.isDefinedAt).map(sqRoot)
Przykład 2
sealed trait SuperType // `sealed` modifier allows inheritance within current build-unit only
case class A(value: Int) extends SuperType
case class B(text: String) extends SuperType
case object C extends SuperType
val input: Seq[SuperType] = Seq(A(5), B("hello"), C, A(25), B(""))
input.collect {
case A(value) if value < 10 => value.toString
case B(text) if text.nonEmpty => text
} // Seq("5", "hello")
W powyższym przykładzie należy zwrócić uwagę na kilka rzeczy:
- Lewa strona każdego dopasowania wzorca skutecznie wybiera elementy do przetworzenia i dołączenia do wyniku. Każda wartość, która nie ma pasującej
case
jest po prostu pomijana. - Prawa strona określa przetwarzanie specyficzne dla przypadku, które ma zostać zastosowane.
- Dopasowywanie wzorca wiąże zmienną do użycia w instrukcjach wartowniczych (klauzule
if
) i po prawej stronie.
Podstawowa składnia
Scala posiada specjalny typ funkcji zwany częściowy funkcji , który rozciąga się normalne funkcje - co oznacza, że PartialFunction
przykład może być stosowany wszędzie tam, gdzie Function1
ma. Funkcje częściowe mogą być zdefiniowane anonimowo przy użyciu składni case
stosowanej również w dopasowaniu wzorca :
val pf: PartialFunction[Boolean, Int] = {
case true => 7
}
pf.isDefinedAt(true) // returns true
pf(true) // returns 7
pf.isDefinedAt(false) // returns false
pf(false) // throws scala.MatchError: false (of class java.lang.Boolean)
Jak widać w przykładzie, funkcja częściowa nie musi być zdefiniowana w całej domenie pierwszego parametru. Zakłada się, że standardowa instancja Function1
jest całkowita , co oznacza, że jest zdefiniowana dla każdego możliwego argumentu.
Zastosowanie jako funkcja całkowita
Funkcje częściowe są bardzo powszechne w idiomatycznej Scali. Są często używane ze względu na wygodną składnię opartą na case
, aby zdefiniować całkowitą liczbę funkcji w stosunku do cech :
sealed trait SuperType // `sealed` modifier allows inheritance within current build-unit only
case object A extends SuperType
case object B extends SuperType
case object C extends SuperType
val input: Seq[SuperType] = Seq(A, B, C)
input.map {
case A => 5
case _ => 10
} // Seq(5, 10, 10)
Zapisuje to dodatkową składnię instrukcji match
w regularnej anonimowej funkcji. Porównać:
input.map { item =>
item match {
case A => 5
case _ => 10
}
} // Seq(5, 10, 10)
Jest również często używany do przeprowadzania dekompozycji parametrów za pomocą dopasowania wzorca, gdy krotka lub klasa obserwacji jest przekazywana do funkcji:
val input = Seq("A" -> 1, "B" -> 2, "C" -> 3)
input.map { case (a, i) =>
a + i.toString
} // Seq("A1", "B2", "C3")
Zastosowanie do wyodrębnienia krotek w funkcji mapy
Te trzy funkcje mapy są równoważne, więc użyj wariantu, który twój zespół uzna za najbardziej czytelny.
val numberNames = Map(1 -> "One", 2 -> "Two", 3 -> "Three")
// 1. No extraction
numberNames.map(it => s"${it._1} is written ${it._2}" )
// 2. Extraction within a normal function
numberNames.map(it => {
val (number, name) = it
s"$number is written $name"
})
// 3. Extraction via a partial function (note the brackets in the parentheses)
numberNames.map({ case (number, name) => s"$number is written $name" })
Funkcja częściowa musi pasować do wszystkich danych wejściowych : każdy przypadek, który nie pasuje, spowoduje wygenerowanie wyjątku w czasie wykonywania.