Recherche…


Composition

Les fonctions partielles sont souvent utilisées pour définir une fonction totale dans les parties:

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)

Dans cette utilisation, les fonctions partielles sont tentées par ordre de concaténation avec la méthode orElse . En règle générale, une fonction partielle finale est fournie qui correspond à tous les cas restants. Collectivement, la combinaison de ces fonctions agit comme une fonction totale.

Ce modèle est généralement utilisé pour séparer les problèmes pour lesquels une fonction peut agir efficacement comme répartiteur pour des chemins de code disparates. Ceci est courant, par exemple, dans la méthode de réception d'un acteur Akka .

Utilisation avec `collect`

Alors que les fonctions partielles sont souvent utilisées comme syntaxe pratique pour les fonctions totales, en incluant une dernière correspondance générique ( case _ ), dans certaines méthodes, leur partialité est la clé. Un exemple très courant dans Scala idiomatique est la méthode de collect , définie dans la bibliothèque de collections Scala. Ici, les fonctions partielles permettent aux fonctions courantes d’examiner les éléments d’une collection pour les mapper et / ou les filtrer pour qu’ils se produisent dans une syntaxe compacte.

Exemple 1

En supposant que nous avons une fonction racine carrée définie comme fonction partielle:

val sqRoot:PartialFunction[Double,Double] = { case n if n > 0 => math.sqrt(n) }

On peut l'invoquer avec le combinateur de collect :

List(-1.1,2.2,3.3,0).collect(sqRoot)

effectuer efficacement la même opération que:

List(-1.1,2.2,3.3,0).filter(sqRoot.isDefinedAt).map(sqRoot)

Exemple 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")

Il y a plusieurs choses à noter dans l'exemple ci-dessus:

  • Le côté gauche de chaque correspondance de modèle sélectionne efficacement les éléments à traiter et à inclure dans la sortie. Toute valeur qui ne dispose pas d' une correspondance le case est simplement omis.
  • Le côté droit définit le traitement spécifique à la casse à appliquer.
  • La correspondance de modèle lie la variable à utiliser dans les instructions de garde (les clauses if ) et à droite.

Syntaxe de base

Scala a un type spécial de fonction appelé fonction partielle , qui étend les fonctions normales - ce qui signifie qu'une instance de PartialFunction peut être utilisée partout où la Function1 est attendue. Les fonctions partielles peuvent être définies de manière anonyme à l'aide de la syntaxe de case utilisée également dans la correspondance de modèle :

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)

Comme on le voit dans l'exemple, une fonction partielle n'a pas besoin d'être définie sur tout le domaine de son premier paramètre. Une instance standard de Function1 est supposée être totale , ce qui signifie qu'elle est définie pour chaque argument possible.

Utilisation comme fonction totale

Les fonctions partielles sont très courantes dans les Scala idiomatiques. Ils sont souvent utilisés pour leur syntaxe pratique à base de case afin de définir les fonctions totales sur les traits :

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)

Cela enregistre la syntaxe supplémentaire d'une déclaration de match dans une fonction anonyme régulière. Comparer:

input.map { item => 
  item match {
    case A => 5
    case _ => 10
  }
} // Seq(5, 10, 10)

Il est également fréquemment utilisé pour effectuer une décomposition de paramètres en utilisant la correspondance de modèle, lorsqu'un tuple ou une classe de cas est transmis à une fonction:

val input = Seq("A" -> 1, "B" -> 2, "C" -> 3)

input.map { case (a, i) =>
   a + i.toString
} // Seq("A1", "B2", "C3")

Utilisation pour extraire des tuples dans une fonction de carte

Ces trois fonctions de carte sont équivalentes, utilisez donc la variante la plus lisible par votre équipe.

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 fonction partielle doit correspondre à toutes les entrées : toute observation qui ne correspond pas déclenche une exception à l'exécution.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow