Un linguaggio a oggetti puro è un linguaggio in cui ogni valore è un oggetto. In particolare, se il linguaggio è basato sulle classi (non tutti i linguaggi a oggetti lo sono), allora il tipo di ogni valore è una classe.
Concettualmente, al livello della macchina astratta, Scala è un linguaggio a oggetti puro, i cui tipi potrebbero tutti essere definiti sotto forma di classi scritte nel linguaggio stesso. Ci sono però alcuni tipi la cui implementazione come classi non è immediata, ed è quindi opportuno discutere: i tipi base e le funzioni.
Il fatto che Scala sia un linguaggio a oggetti puro è importante perchè significa che il modoello di sostituzione è applicabile a tutto il linguaggio (escluse le parti imperative), e questa è una condizione necessaria per poter sfruttare i meccanismi di ragionamento formali basati su tale modello, che come detto in precedenza sono uno dei principali vantaggi della programmazione funzionale pura.
Concettualmente i tipi base di Scala (Int
, Boolean
, ecc) sono trattati come oggetti, istanze di classi definite nel package scala
. Tuttavia, per motivi di efficienza, nel modello di esecuzione essi sono in realtà rappresentati utilizzando i tipi primitivi della JVM, ma ciò è trasparente al livello a cui ragiona il programmatore, che non vede differenze tra i tipi base e gli altri.
Per dimostrare che Scala è un linguaggio a oggetti puro nonostante l’uso dei tipi primitivi nell’implementazione, bisogna definire delle classi che implementino i tipi base senza fare uso dei tipi primitivi, e poi verificare che i metodi di tali clsasi abbiano lo stesso comportamento delle operazioni su tipi primitivi fornite dalla JVM. Fare ciò per tutti i tipi base è piuttosto complicato e laborioso, ma l’idea generale può essere illustrata mostrando una possibile implementazione OO pura del tipo Boolean
(scelto perchè è piuttosto semplice).
Boolean
puroIl tipo Boolean
di Scala corrisponde al tipo primitivo boolean
della JVM. Una sua implementazione OO pura potrebbe essere fornita dalla seguente gerarchia di classi:
package idealized.scala
trait Boolean {
def ifThenElse[T](t: => T, e: => T): T
def &&(x: => Boolean): Boolean = ifThenElse(x, false)
def ||(x: => Boolean): Boolean = ifThenElse(true, x)
def unary_! : Boolean = ifThenElse(false, true)
def ==(x: => Boolean): Boolean = ifThenElse(x, !x)
def !=(x: => Boolean): Boolean = ifThenElse(!x, x)
}
object true extends Boolean {
override def ifThenElse[T](t: => T, e: => T): T = t
}
object false extends Boolean {
override def ifThenElse[T](t: => T, e: => T): T = e
}
idealized.scala
vuole mettere in evidenza il fatto che questa non è implementazione reale, ma appunto una “ideale” finalizzata al ragionamento formale.Boolean
definisce le operazioni disponibili sul tipo. Qui per semplicità sono definite solo alcune delle principali operazioni, ma si potrebbero definire in modo simil anche tutte le altre.true
e false
, che estendono Boolean
, rappresentano gli unici due valori del tipo.In un linguaggio a oggetti puro, che tratta tutti i valori come oggetti, tutti gli operatori devono essere riscrivibili come invocazioni di metodi, compreso l’operatore condizionale if-else
. A tale scopo, il trait Boolean
definisce un metodo ifThenElse
: l’espressione
$$ \texttt{if (cond) thenExpr else elseExpr} $$
è equivalente a
$$ \texttt{cond.ifThenElse(thenExpr, elseExpr)} $$
Si noti che i due parametri di ifThenElse
, corrispondenti alle espressioni nei rami “then” e “else” dell’if-else
, sono passati con la strategia call-by-name, in modo che valutata solo l’espressione nel ramo corrispondente al valore della condizione, esattamente come fa l’operatore if-else
predefinito. Inoltre, il tipo di entrambi questi parametri è il tipo parametro T
, che è anche il tipo restituito dal metodo; se esso non viene specificato esplicitamente, il compilatore deduce il più piccolo supertipo comune ai tipi delle espressioni dei due rami, come appunto avviene per l’operatore if-else
.
ifThenElse
è l’unico metodo astratto di Boolean
, e in quanto tale viene implementato concretamente negli object true
e false
: quando viene invocato sull’oggetto true
restituisce il valore dell’espressione nel ramo “then” (il parametro t
), mentre sull’oggetto false
restituisce il valore dell’espressione nel ramo “else” (il parametro e
). Tutti gli altri metodi di Boolean
possono essere impementati concretamente direttamente nel trait, utilizzando solo ifThenElse
per esprimere le regole di valutazione dei vari operatori:
L’espressione a && b
deve essere riscritta come b
se a
vale true
e come false
se a
è false
, quindi equivale a:
$$ \texttt{a \&\& b} \equiv \texttt{if(a) b else false }\equiv \texttt{a.ifThenElse(b, false)} $$