Scala Language
Gedeeltelijke functies
Zoeken…
Samenstelling
Gedeeltelijke functies worden vaak gebruikt om een totale functie in delen te definiëren:
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 dit gebruik worden de gedeeltelijke functies geprobeerd in volgorde van aaneenschakeling met de methode orElse
. Meestal wordt een laatste gedeeltelijke functie geboden die overeenkomt met alle resterende gevallen. Gezamenlijk fungeert de combinatie van deze functies als een totale functie.
Dit patroon wordt meestal gebruikt om zorgen te scheiden, waarbij een functie effectief een verzender voor verschillende codepaden kan zijn. Dit komt bijvoorbeeld veel voor bij de ontvangstmethode van een Akka-acteur .
Gebruik met `verzamelen`
Hoewel gedeeltelijke functie vaak wordt gebruikt als handige syntaxis voor totale functies, door een laatste wildcard-overeenkomst ( case _
) op te nemen, is in sommige methoden hun partijdigheid van cruciaal belang. Een veel voorkomend voorbeeld in idiomatische Scala is de collect
, gedefinieerd in de bibliotheek van Scala-collecties. Hier laten gedeeltelijke functies de algemene functies van het onderzoeken van de elementen van een verzameling toe om ze in kaart te brengen en / of te filteren in één compacte syntaxis.
voorbeeld 1
Ervan uitgaande dat we een vierkantswortelfunctie hebben die is gedefinieerd als gedeeltelijke functie:
val sqRoot:PartialFunction[Double,Double] = { case n if n > 0 => math.sqrt(n) }
We kunnen het aanroepen met de collect
:
List(-1.1,2.2,3.3,0).collect(sqRoot)
effectief dezelfde bewerking uitvoeren als:
List(-1.1,2.2,3.3,0).filter(sqRoot.isDefinedAt).map(sqRoot)
Voorbeeld 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")
Er zijn verschillende dingen om op te merken in het bovenstaande voorbeeld:
- De linkerkant van elke patroonmatch selecteert effectief elementen om te verwerken en op te nemen in de uitvoer. Elke waarde die geen overeenkomend
case
wordt eenvoudig weggelaten. - De rechterkant definieert de toe te passen casusspecifieke verwerking.
- Patroonaanpassing bindt variabele voor gebruik in bewakingsinstructies (de
if
clausules) en de rechterkant.
Basissyntaxis
Scala een speciaal soort functie genaamd een deelfunctie , dat zich normaal functioneert - wat betekent dat een PartialFunction
kan bijvoorbeeld worden gebruikt waar Function1
verwacht. Gedeeltelijke functies kunnen anoniem worden gedefinieerd met behulp van case
syntaxis die ook wordt gebruikt bij het matchen van patronen :
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)
Zoals te zien in het voorbeeld, hoeft een gedeeltelijke functie niet te worden gedefinieerd voor het hele domein van de eerste parameter. Een standaard Function1
instantie wordt verondersteld totaal te zijn , wat betekent dat deze voor elk mogelijk argument is gedefinieerd.
Gebruik als een totale functie
Gedeeltelijke functies komen veel voor in idiomatische Scala. Ze worden vaak gebruikt voor hun handige, op een case
gebaseerde syntaxis om totale functies voor kenmerken te definiëren:
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)
Dit slaat de extra syntaxis van een match
in een reguliere anonieme functie. Vergelijken:
input.map { item =>
item match {
case A => 5
case _ => 10
}
} // Seq(5, 10, 10)
Het wordt ook vaak gebruikt om een parameterontleding uit te voeren met behulp van patroonovereenkomst, wanneer een tuple of een case-klasse wordt doorgegeven aan een functie:
val input = Seq("A" -> 1, "B" -> 2, "C" -> 3)
input.map { case (a, i) =>
a + i.toString
} // Seq("A1", "B2", "C3")
Gebruik om tupels in een kaartfunctie te extraheren
Deze drie kaartfuncties zijn equivalent, dus gebruik de variatie die uw team het meest leesbaar vindt.
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" })
De gedeeltelijke functie moet overeenkomen met alle invoer : elk geval dat niet overeenkomt, genereert een uitzondering tijdens runtime.