Scala Language
Extracteurs
Recherche…
Syntaxe
- extracteur de val (extractValue1, _ / * seconde valeur extraite ignorée * /) = valeurToBeExtracter
- match valueToBeExtracted {extracteur de cas (extractValue1, _) => ???}
- val (tuple1, tuple2, tuple3) = tupleWith3Elements
- objet Foo {def unapply (foo: Foo): Option [String] = Some (foo.x); }
Extracteurs de tuple
x
et y
sont extraits du tuple:
val (x, y) = (1337, 42) // x: Int = 1337 // y: Int = 42
Pour ignorer une valeur, utilisez _
:
val (_, y: Int) = (1337, 42) // y: Int = 42
Déballer un extracteur:
val myTuple = (1337, 42) myTuple._1 // res0: Int = 1337 myTuple._2 // res1: Int = 42
Notez que tuples ont une longueur maximale de 22 ans , et donc ._1
par ._22
fonctionnera ( en supposant que le tuple est au moins de cette taille).
Les extracteurs de tuples peuvent être utilisés pour fournir des arguments symboliques aux fonctions littérales:
val persons = List("A." -> "Lovelace", "G." -> "Hopper") val names = List("Lovelace, A.", "Hopper, G.") assert { names == (persons map { name => s"${name._2}, ${name._1}" }) } assert { names == (persons map { case (given, surname) => s"$surname, $given" }) }
Extracteurs de classe de cas
Une classe de cas est une classe avec beaucoup de code standard standard inclus automatiquement. Un des avantages de ceci est que Scala facilite l'utilisation des extracteurs avec des classes de cas.
case class Person(name: String, age: Int) // Define the case class
val p = Person("Paola", 42) // Instantiate a value with the case class type
val Person(n, a) = p // Extract values n and a
// n: String = Paola
// a: Int = 42
A ce stade, n
et a
sont tous deux val
s dans le programme et sont accessibles en tant que tels: on dit qu'ils ont été «extraits» de p. Continue:
val p2 = Person("Angela", 1337)
val List(Person(n1, a1), Person(_, a2)) = List(p, p2)
// n1: String = Paola
// a1: Int = 42
// a2: Int = 1337
Nous voyons ici deux choses importantes:
- L'extraction peut avoir lieu à des niveaux «profonds»: les propriétés des objets imbriqués peuvent être extraites.
- Tous les éléments ne doivent pas être extraits. Le caractère générique
_
indique que cette valeur particulière peut être n'importe quoi et est ignorée. Aucunval
est créé.
En particulier, cela peut faciliter l’adaptation des collections:
val ls = List(p1, p2, p3) // List of Person objects
ls.map(person => person match {
case Person(n, a) => println("%s is %d years old".format(n, a))
})
Ici, nous avons du code qui utilise l'extracteur pour vérifier explicitement que cette person
est un objet Person
et extraire immédiatement les variables qui nous intéressent: n
et a
.
Unapply - Extracteurs personnalisés
Une extraction personnalisée peut être écrite en implémentant la méthode unapply
et en renvoyant une valeur de type Option
:
class Foo(val x: String)
object Foo {
def unapply(foo: Foo): Option[String] = Some(foo.x)
}
new Foo("42") match {
case Foo(x) => x
}
// "42"
Le type de retour d' unapply
peut être différent de Option
, à condition que le type renvoyé fournisse des méthodes get
et isEmpty
. Dans cet exemple, Bar
est défini avec ces méthodes, et unapply
renvoie une instance de Bar
:
class Bar(val x: String) {
def get = x
def isEmpty = false
}
object Bar {
def unapply(bar: Bar): Bar = bar
}
new Bar("1337") match {
case Bar(x) => x
}
// "1337"
Le type de retour de unapply
peut aussi être un Boolean
, ce qui est un cas particulier qui ne répond pas aux exigences de get
et isEmpty
ci-dessus. Cependant, notez dans cet exemple que DivisibleByTwo
est un objet, pas une classe, et ne prend pas de paramètre (et donc ce paramètre ne peut pas être lié):
object DivisibleByTwo {
def unapply(num: Int): Boolean = num % 2 == 0
}
4 match {
case DivisibleByTwo() => "yes"
case _ => "no"
}
// yes
3 match {
case DivisibleByTwo() => "yes"
case _ => "no"
}
// no
Rappelez-vous que unapply
va pas dans l'objet compagnon d'une classe, pas dans la classe. L'exemple ci-dessus sera clair si vous comprenez cette distinction.
Notation Infix Extractor
Si une classe de cas a exactement deux valeurs, son extracteur peut être utilisé en notation infixe.
case class Pair(a: String, b: String)
val p: Pair = Pair("hello", "world")
val x Pair y = p
//x: String = hello
//y: String = world
Tout extracteur qui retourne un 2-tuple peut fonctionner de cette façon.
object Foo {
def unapply(s: String): Option[(Int, Int)] = Some((s.length, 5))
}
val a Foo b = "hello world!"
//a: Int = 12
//b: Int = 5
Extracteurs de regex
Une expression régulière avec des parties groupées peut être utilisée comme extracteur:
scala> val address = """(.+):(\d+)""".r
address: scala.util.matching.Regex = (.+):(\d+)
scala> val address(host, port) = "some.domain.org:8080"
host: String = some.domain.org
port: String = 8080
Notez que si elle ne correspond pas, une MatchError
sera lancée à l'exécution:
scala> val address(host, port) = "something not a host and port"
scala.MatchError: something not a host and port (of class java.lang.String)
Extracteurs transformateurs
Le comportement des extracteurs peut être utilisé pour obtenir des valeurs arbitraires à partir de leurs entrées. Cela peut être utile dans les scénarios où vous souhaitez pouvoir agir sur les résultats d'une transformation en cas de réussite de la transformation.
Prenons l'exemple des différents formats de noms d'utilisateur utilisables dans un environnement Windows :
object UserPrincipalName {
def unapply(str: String): Option[(String, String)] = str.split('@') match {
case Array(u, d) if u.length > 0 && d.length > 0 => Some((u, d))
case _ => None
}
}
object DownLevelLogonName {
def unapply(str: String): Option[(String, String)] = str.split('\\') match {
case Array(d, u) if u.length > 0 && d.length > 0 => Some((d, u))
case _ => None
}
}
def getDomain(str: String): Option[String] = str match {
case UserPrincipalName(_, domain) => Some(domain)
case DownLevelLogonName(domain, _) => Some(domain)
case _ => None
}
En fait, il est possible de créer un extracteur présentant les deux comportements en élargissant les types qu'il peut correspondre:
object UserPrincipalName {
def unapply(obj: Any): Option[(String, String)] = obj match {
case upn: UserPrincipalName => Some((upn.username, upn.domain))
case str: String => str.split('@') match {
case Array(u, d) if u.length > 0 && d.length > 0 => Some((u, d))
case _ => None
}
case _ => None
}
}
En général, les extracteurs sont simplement une reformulation pratique du modèle Option
, appliqué aux méthodes avec des noms tels que tryParse
:
UserPrincipalName.unapply("user@domain") match {
case Some((u, d)) => ???
case None => ???
}