Lo scopo dei metodi di classificazione e di accesso, e in un modo o nell’altro anche delle altre soluzioni al problema della decomposizione viste finora, è sostanzialmente quello di invertire il processo di costruzione degli oggetti:
In altre parole, tutti gli elementi necessari a classificare un oggetto e ad accedere alle informazioni che esso contiene sono fornite dalla costruzione dell’oggetto, ovvero dal valore che descrive l’oggetto nel modello di sostituzione (dato che la rappresentazione di un oggetto in tale modello corrisponde appunto all’invocazione del costruttore). Il pattern matching è il meccanismo tramite il quale i linguaggi funzionali consentono di utilizzare le informazioni specificate in tale valore.
L’implementazione del pattern matching in Scala è fortemente influenzata dal fatto che Scala sia un linguaggio orientato agli oggetti: il pattern matching agisce principalmente su un tipo particolare di classi, le classi case.
La definizione di una classe case (case class) è analoga alla definizione di una classe normale, ma è preceduta dalla parola riservata case. Ad esempio, le definizioni
trait Expr
case class Number(n: Int) extends Expr
case class Sum(e1: Expr, e2: Expr) extends Expr
case class Prod(e1: Expr, e2: Expr) extends Expr
case class Var(name: String) extends Expr
realizzano la gerarchia delle espressioni aritmetiche, che è costrituita dal trait Expr
e da quattro classi concrete che estendono il trait. Siccome il pattern matching opera sulle istanze delle classi, tipicamente si definiscono con case
solo le classi concrete (non astratte) di una gerarchia (e case
non è proprio ammesso nella definizione dei trait).
Come effetto della definizione di una classe case il compilatore fornisce automaticamente diversi supporti sintattici:
val
, cioè memorizzati come campiequals
, hashCode
e toString
copy
, utile per creare copie modificate delle istanze della classeOltre che per le classi, la parola riservata case
può essere utlizzata anche nella definizione degli object
(in tal caso, tra i supporti sintattici appena elencati il compilatore fornisce solo quelli che hanno senso per gli oggetti singleton — ad esempio non vengono forniti nè un metodo factory nè un metodo copy
, perchè non si possono costruire o copiare istanze di un singleton object).
Per ogni case class il compilatore definisce implicitamente un companion object con un metodo factory apply
che invoca il costruttore primario della classe e ha la stessa segnatura di tale costuttore. Tale metodo può poi essere usato (come già visto nell’implementazione di List
presentata in precedenza) per costruire istanze della classe senza bisogno di scrivere l’operatore new
.
Ad esempio, nel caso della gerarchia di Expr
riportata sopra vengono implicitamente definiti i compaion object