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.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow