Scala Language
estrattori
Ricerca…
Sintassi
- val extractor (extractedValue1, _ / * secondo valore estratto estrapolato * /) = valueToBeExtracted
- valueToBeStruttura abbinata {case extractor (extractedValue1, _) => ???}
- val (tuple1, tuple2, tuple3) = tupleWith3Elements
- oggetto Foo {def unapply (foo: Foo): Option [String] = Some (foo.x); }
Estrattori a tupla
x
e y
sono estratti dalla tupla:
val (x, y) = (1337, 42) // x: Int = 1337 // y: Int = 42
Per ignorare un valore, utilizzare _
:
val (_, y: Int) = (1337, 42) // y: Int = 42
Per decomprimere un estrattore:
val myTuple = (1337, 42) myTuple._1 // res0: Int = 1337 myTuple._2 // res1: Int = 42
Nota che le tuple hanno una lunghezza massima di 22, e quindi ._1
da ._22
a ._22
(supponendo che la tupla abbia almeno quella dimensione).
Gli estrattori di tupla possono essere usati per fornire argomenti simbolici per le funzioni letterali:
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" }) }
Estrattori Case Class
Una classe di casi è una classe con un codice standard di codice standard che è incluso automaticamente. Un vantaggio di questo è che Scala semplifica l'uso di estrattori con classi di casi.
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
In questo frangente, sia n
che a
sono val
nel programma e sono accessibili come tali: si dice che siano stati "estratti" da p. continuando:
val p2 = Person("Angela", 1337)
val List(Person(n1, a1), Person(_, a2)) = List(p, p2)
// n1: String = Paola
// a1: Int = 42
// a2: Int = 1337
Qui vediamo due cose importanti:
- L'estrazione può avvenire a livelli "profondi": è possibile estrarre le proprietà degli oggetti nidificati.
- Non tutti gli elementi devono essere estratti. Il jolly
_
carattere indica che quel particolare valore può essere qualsiasi cosa, e viene ignorato. Nessunval
è stato creato.
In particolare, questo può semplificare la corrispondenza tra le raccolte:
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))
})
Qui, abbiamo un codice che usa l'extractor per verificare esplicitamente che la person
sia un oggetto Person
e immediatamente estrae le variabili che ci interessano: n
e a
.
Unapply - Estrattori personalizzati
È possibile scrivere un'estrazione personalizzata implementando il metodo unapply
e restituendo un valore di tipo 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"
Il tipo restituito di unapply
potrebbe essere qualcosa di diverso da Option
, a condizione che il tipo restituito fornisca i metodi get
e isEmpty
. In questo esempio, la Bar
viene definita con tali metodi e riappare in modo unapply
un'istanza di 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"
Il tipo restituito di unapply
può anche essere un Boolean
, che è un caso speciale che non ha i requisiti get
ed isEmpty
sopra. Tuttavia, si noti in questo esempio che DivisibleByTwo
è un oggetto, non una classe, e non accetta un parametro (e quindi tale parametro non può essere associato):
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
Ricorda che unapply
va nell'oggetto compagno di una classe, non nella classe. L'esempio sopra sarà chiaro se capisci questa distinzione.
Notazione Infix dell'estrattore
Se una classe del caso ha esattamente due valori, il suo estrattore può essere utilizzato nella notazione infisso.
case class Pair(a: String, b: String)
val p: Pair = Pair("hello", "world")
val x Pair y = p
//x: String = hello
//y: String = world
Qualsiasi estrattore che restituisce una tupla da 2 può funzionare in questo modo.
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
Estrattori Regex
Un'espressione regolare con parti raggruppate può essere utilizzata come estrattore:
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
Si noti che quando non è abbinato, viene MatchError
un MatchError
in fase di runtime:
scala> val address(host, port) = "something not a host and port"
scala.MatchError: something not a host and port (of class java.lang.String)
Estrattori trasformativi
Il comportamento dell'estrattore può essere utilizzato per ricavare valori arbitrari dal loro input. Ciò può essere utile negli scenari in cui si desidera essere in grado di agire sui risultati di una trasformazione nel caso in cui la trasformazione abbia esito positivo.
Considerare come esempio i vari formati di nome utente utilizzabili in un ambiente 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
}
In effetti è possibile creare un estrattore che mostri entrambi i comportamenti allargando i tipi che può abbinare:
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
}
}
In generale, gli estrattori sono semplicemente una conveniente riformulazione del pattern Option
, applicato ai metodi con nomi come tryParse
:
UserPrincipalName.unapply("user@domain") match {
case Some((u, d)) => ???
case None => ???
}