Linguaggi a oggetti puri

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.

Tipi base

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).

Tipo Boolean puro

Il 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
}

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: