Scala Language
Funzioni parziali
Ricerca…
Composizione
Le funzioni parziali sono spesso utilizzate per definire una funzione totale in parti:
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)
In questo utilizzo, le funzioni parziali vengono tentate in ordine di concatenazione con il metodo orElse
. In genere, viene fornita una funzione parziale finale che corrisponde a tutti i casi rimanenti. Collettivamente, la combinazione di queste funzioni agisce come una funzione totale.
Questo schema viene in genere utilizzato per separare le preoccupazioni in cui una funzione può effettivamente agire come un dispatcher per percorsi di codice disparati. Questo è comune, ad esempio, nel metodo di ricezione di un attore Akka .
Utilizzo con `collect`
Mentre le funzioni parziali sono spesso usate come sintassi conveniente per le funzioni totali, includendo una corrispondenza jolly finale ( case _
), in alcuni metodi, la loro parzialità è la chiave. Un esempio molto comune in Scala idiomatica è il metodo collect
, definito nella libreria delle collezioni Scala. Qui, le funzioni parziali consentono alle funzioni comuni di esaminare gli elementi di una raccolta per mapparle e / o filtrarle in modo che si verifichino in una sintassi compatta.
Esempio 1
Supponendo che abbiamo una funzione radice quadrata definita come funzione parziale:
val sqRoot:PartialFunction[Double,Double] = { case n if n > 0 => math.sqrt(n) }
Possiamo invocarlo con il combinatore di collect
:
List(-1.1,2.2,3.3,0).collect(sqRoot)
eseguendo efficacemente la stessa operazione di:
List(-1.1,2.2,3.3,0).filter(sqRoot.isDefinedAt).map(sqRoot)
Esempio 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")
Ci sono diverse cose da notare nell'esempio sopra:
- Il lato sinistro di ogni modello corrisponde in modo efficace agli elementi da elaborare e includere nell'output. Qualsiasi valore che non ha un
case
corrispondente viene semplicemente omesso. - Il lato destro definisce l'elaborazione specifica del caso da applicare.
- La corrispondenza del modello lega la variabile per l'uso nelle istruzioni di guardia (le clausole
if
) e nella parte destra.
Sintassi di base
Scala ha un particolare tipo di funzione chiamata una funzione parziale , che si estende normali funzioni - significa che una PartialFunction
istanza può essere utilizzato ovunque Function1
è previsto. Le funzioni parziali possono essere definite in modo anonimo utilizzando la sintassi del case
utilizzata anche nella corrispondenza del modello :
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)
Come visto nell'esempio, non è necessario definire una funzione parziale sull'intero dominio del suo primo parametro. Si presuppone che un'istanza Function1
standard sia totale , ovvero che sia definita per ogni argomento possibile.
Utilizzo come funzione totale
Le funzioni parziali sono molto comuni in Scala idiomatica. Sono spesso usati per la loro sintassi basata su un case
conveniente per definire le funzioni totali sui tratti :
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)
Questo salva la sintassi aggiuntiva di un'istruzione match
in una normale funzione anonima. Confrontare:
input.map { item =>
item match {
case A => 5
case _ => 10
}
} // Seq(5, 10, 10)
Viene anche utilizzato frequentemente per eseguire una scomposizione dei parametri utilizzando la corrispondenza del modello, quando una tupla o una classe case viene passata a una funzione:
val input = Seq("A" -> 1, "B" -> 2, "C" -> 3)
input.map { case (a, i) =>
a + i.toString
} // Seq("A1", "B2", "C3")
Utilizzo per estrarre le tuple in una funzione mappa
Queste tre funzioni della mappa sono equivalenti, quindi usa la variazione che il tuo team trova più leggibile.
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" })
La funzione parziale deve corrispondere a tutti gli input : ogni caso che non corrisponde genererà un'eccezione in fase di esecuzione.