Il costrutto Scala coinvolto qui è il Extractor. Il simbolo ::
non è altro che una classe di case in Scala, dove esiste un metodo unapply
sul suo oggetto companion per rendere la magia di estrazione accadere. Here è un buon tutorial approfondito sugli estrattori. Ma ecco il riassunto:
Ogni volta che si desidera "decomprimere" il contenuto di una classe, sia per il binding variabile o come parte di pattern matching, il compilatore cerca il metodo unapply
su qualunque simbolo si trovi sul lato sinistro dell'espressione. Questo può essere un oggetto, un oggetto compagno della classe case (come ::
, nella tua domanda) o un'istanza con un unapply
. L'argomento su unapply
è il tipo in ingresso da decomprimere e il tipo restituito è un Option
di ciò che è stato dichiarato come struttura e tipi previsti. Nella corrispondenza del modello a None
indica che non è stata trovata una corrispondenza. Nell'associazione variabile viene generato un numero MatchError
se il risultato è None
.
Un buon modo di pensare su unapply
è che è l'inverso di apply
. Dove unapply
il sintonizzatore della funzione chiamata sintassi, unapply
è il destinatario di chiamate extractor.
Per illustrare questo ulteriore, definiamo una semplice classe caso:
case class Cat(name: String, age: Int)
Perché è una classe case, veniamo generati automaticamente apply
e unapply
metodi sull'oggetto compagna, che grosso modo simile a questa:
object Cat {
// compiler generated...
def apply(name: String, age: Int) = new Cat(name, age)
def unapply(aCat: Cat): Option[(String, Int)] = Some((aCat.name, aCat.age))
}
Quando si crea un Cat
tramite l'oggetto associato, viene chiamato apply
. Quando si estrae gli elementi costitutivi di un Cat
, unapply
si chiama:
val mycat = Cat("freddy", 3) // `apply` called here
...
val Cat(name, age) = mycat // `unapply` called here
...
val animal: AnyRef = mycat
val info = animal match {
case Cat(name, age) => "My pet " + name // `unapply` called here
case _ => "Not my pet"
}
// info: String = My pet freddy
perché unapply
restituisce un Option
, abbiamo un sacco di potere di scrivere estrattori che gestiscono i casi più interessanti, per esempio, testare se il tipo di ingresso si conforma ad alcuni criteri prima di estrarre i valori. Ad esempio, supponiamo di voler ottenere il nome di gatti "vecchi".Si potrebbe fare questo:
object OldCatName {
def unapply(aCat: Cat) = if (aCat.age >= 10) Some(aCat.name) else None
}
Uso sarebbe lo stesso di un generato unapply
:
val yourcat = Cat("betty", 12)
...
val OldCatName(name1) = yourcat
// name1: String = "betty"
val OldCatName(name2) = mycat
// scala.MatchError: Cat(freddy,3) (of class Cat)
MatchError
s non sono una bella cosa da consentire, quindi cerchiamo di usare il pattern matching:
val conditions = Seq(mycat, yourcat) map {
case OldCatName(oldie) => s"$oldie is old"
case Cat(name, age) => s"At age $age $name is not old"
}
// conditions: Seq[String] = List(At age 3 freddy is not old, betty is old)
L'un po 'di magia in più con il metodo unapply
per ::
è che un po' di zucchero sintattico consente val ::(head, tail) = ...
da scrivere val head :: tail = ...
.
http://blog.lunatech.com/2011/11/25/scala-list-extractor-demistificato buona spiegazione come funziona –
@ eugene-zhulenev, grazie per il collegamento. Questo era perfetto. La classe case Contro fornisce automaticamente i mezzi per costruire un'istanza List e decostruirla in una testa e una coda. – poissondist
@poissondist il collegamento da eugene zhulenev è rotto. Cosa ne pensi della mia risposta qui sotto? – metasim