lunedì 31 ottobre 2011

Biscotti, Classi e Oggetti

In questo post si vuole indagare il supporto alla programmazione a oggetti fornito da Scala, prendo come guida esplicitamente il libro di Odersky che si può acquistare qui.
La metafora più abusata per descrivere per la prima volta la relazione tra classi e oggetti è certamente quella dello stampo per biscotti e i biscotti. Cioè come lo stampo una volta creato ci permette di formare il numero di biscotti che desideriamo, così la classe una volta definita ci permette di creare un oggetto ogni qual volta ne abbiamo necessità. Sperando di non attirarci le ire di nessuno muoviamo una piccola critica alla storica metafora. Lo stampo per biscotti riesce a renderci tutti i biscotti che vogliamo nella forma desiderata, ma non ci garantisce nulla sul loro gusto, sul loro grado di friabilità, su quanto velocemente assorbe il latte una volta inzuppato, in poche parole lo stampo non ci garantisce nulla sul comportamento del futuro biscotto e ne definisce solo la sua forma, la sua struttura.
Ora, uno dei principi fondanti della programmazione a oggetti è l’incapsulamento e cioè di incorporare negli oggetti i dati (struttura) e le funzionalità sotto forma di metodi di accesso ai dati (comportamento) per evitare un utilizzo improprio dei dati.
Senza stravolgere la millenaria prassi, la nostra metafora manterrà lo stampo per biscotti, ma allo stesso allega una ricetta per preparare l’impasto e fornire una cottura adeguata, in questo modo saremo certi che i nostri biscotti saranno proprio quelli che desideriamo.
Torniamo a Scala, come in Java, la parola chiave class è designata alla dichiarazione delle nostre classi, definiremo così i nostri campi e i nostri metodi con def.

class Persona(){
private var: String nome;
var: Int eta;
val: Date dataDiNascita;
def ciao():Unit = println(“hello world”);
}

e istanzieremo la classe Persona con la parola chiave new

val ugo = new Persona;
ugo.nome= “Ugo”
ugo.eta= 33

osserviamo la sintassi molto simile a Java, ma ci sono particolari significativi che differiscono nei due linguaggi, il modificatore di accesso di default per scala è public, quindi possiamo ometterlo per campi e metodi pubblici, e indicare private quando vogliamo rendere privato un campo o un metodo, infatti l’espressione ugo.nome= “Ugo” causerà un errore in quanto si tenta di accedere a un campo privato. L’aver dichiarato come val l’oggetto ugo, non ci impedisce di modificarne il campo età, in questo caso l’oggetto è immutabile nel senso che la variabile ugo non potrà fare riferimento a nessun altro oggetto di tipo Persona, ma essendo eta un campo var, si puo’ riassegnare il suo valore.
Il tipo di ritorno del metodo ciao() è Unit, in Scala questo tipo corrisponde all’ incirca al tipo void per Java, e ci dice che la funzione non restituisce nessun valore, ma evidentemente ha degli effetti collaterali, in questo caso scrive sullo standard output, da notare che omettendo il tipo di ritorno Unit, il compilatore l’avrebbe inferito e quindi la definizione di ciao() sarebbe stata equivalente.
Costruttori di classe
In Scala tutto il codice all’interno della graffe, fa parte del costruttore primario e inoltre possiamo passare i parametri per il costruttore primario direttamente nelle tonde subito dopo la definizione del nome della classe.

class Punto( val x: Float, val y: Float)


class Cerchio( raggio: Float, centro: Punto){
require(raggio != 0) // stabiliamo una precondizione
private val r=raggio;
private val c: Punto = centro;
def area() : Float = scala.math.Pi *r*r;
def draw() = paint(this);
}

mioCerchio = New Cerchio(2.4, new Punto(-1,-1))

la possibilità di definire un costruttore ausiliare in scala esiste, ma viene gestita in maniera differente da come siamo abituati in Java, possiamo costruire tutti i costruttori ausiliari che vogliamo, ma devono sempre richiamare direttamente o mediante una catena di costruttori ausiliari il costruttore primario. Se volessimo, ad esempio, aggiungere alla classe Cerchio un costruttore ausiliare che definisce cerchi centrati nell’ origine degli assi prendendo il solo raggio come parametro

def this(raggio: Float) = this(raggio, new Punto(0,0))

Scala supporta la programmazione a oggetti con due altri concetti fondamentali, gli oggetti singleton e I tratti, definibili con le parole chiave object e trait. Vedremo come agiscono e come rendano differente l'approccio rispetto a Java.
In Scala una classe non può avere campi o membri statici, a questo (ma non solo) ovviano gli oggetti singleton, la sintassi è simile a quella per le classi

object Cerchio {
var : String stileCerchi=”tratteggiato”;
def setStileCerchi(stile: String) stileCerchi=stile;
def getStileCerchi(): String = this.stileCerchi
}

quando un oggetto singleton è definito nello stesso file della classe e ne condivide il nome, diciamo che l'oggetto è l'oggetto companion della classe e viceversa e i campi e metodi si comportano come campi e membri statici, possono essere acceduti con la sintassi;

Cerchio.setStileCerchi(“ombreggiato”)

oggetti e classi possono accedere a campi e metodi dei propri companion.
Gli oggetti singleton non sono tenuti ad avere una classe companion e in questo caso vengono detti standalone e possono essere utilizzati in svariati modi, come raccogliere dei metodi di utilità , o essere l'entry-point per una applicazione:

object PrimaAppScala{
def main(args: Array[String]) {
println “ecco i parametri di questa applicazione”
for (arg ← args)
println(arg)
}
}

questo è il modo con cui si può scrivere una applicazione in scala, il metodo main (args[]) come in Java, ma associato a un oggetto singleton.
Possiamo scrivere questo codice in un file

esempio.scala

non è obbligatorio nominare il file come la classe o l'oggetto che contiene e si possono raccogliere piu' definizioni di classiesempio.scala e oggetti all' interno dello stesso fille. Compilarlo da linea di comando:

# scalac esempio.scala

ed eseguirlo:

#scala esempio primo secondo terzo

avremo in output:

ecco i parametri di questa applicazione
primo
secondo
terzo
#

Traits (tratti) possiamo pensare ai tratti come a delle interfacce java, ma che possono avere sia campi che metodi con la relativa implementazione, tanto da far pensare a una sorta di ereditarietà multipla.
Differenti tratti possono essere pluggati in una classe rendendola capace di gestire qualche particolare aspetto. Vediamo un esempio:

class Persona(var nome:Strint, var eta:Int){
private var: String nome;
var: Int eta;
def saluta():Unit = println(“hello world”);
}

trait Elettore {
private numeroTesseraElettorale: BigInt
private List partecipazioneElezioni;
def getTessera(): BigInt = numeroTesseraElettorale
def setTessera(codice: BigInt) numeroTesseraElettorale=codice
def vota() = println “fatto”

}

class Votante() extend Persona with Elettore {
require(eta >= 18)
override def saluta() = println “hello political world”
}

val mRossi = new Votante (“Mario Rossi”, 35)

i tratti definiscono anche un tipo, infatti possiamo creare una variabile di tipo Elettore e assegnargli qualsiasi oggetto appartenente a una classe che utilizza il tratto.

val mr: Elettore = mRossi

i tratti insieme alle classi astratte ci forniscono un incredibile strumento per il riutilizzo e la scrittura di codice coinciso che rende pluggabili nuovi comportamenti alle nostre classi , vediamo un esempio.

abstract class coda {
def push( x: Int)
def get(): Int
}
e realizziamo una classe concreta CodaSemplice che la realizza

import scala.collection.mutable.ArrayBuffer
class CodaSemplice extends Coda{
private val buf = new ArrayBuffer[Int]
def get() = buf.remove(0)
def put(x: Int) { buf += x }
}

trait FiltraNegativi extends Coda{
abstract override def put(x: Int) {
if (x >= 0) super.put(x)
}
}

vogliamo definire un tratto che inserisce l'elemento nella coda solo se è non negativo , analizziamo la definizione del tratto FiltraNegativi, notiamo subito che il tratto estende la classe astratta coda, questo significa che il tratto potrà essere utilizzato solo e soltanto da quelle classi che realizzano la classe astratta coda.
Il secondo aspetto importante di queste due righe di codice è che nella definizione del tratto troviamo una chiamata super.put(x) riferita a una classe astratta, questo a prima vista sembra almeno un po' strano e non sarebbe permesso nella definizione di una classe, ma nei tratti è permesso associando le parole chiave abstract override e questa chiamata verrà legata dinamicamente all' implementazione del metodo da parte della classe concreta che utilizzerà il tratto. Semplicemente dichiarando una variabile in questo modo

val codaPositivi= (new CodaSemplice Incrementing with FiltraNegativi )

otterremo una coda con il comportamento desiderato senza nessuna modifica al codice della classe concreta.


martedì 18 ottobre 2011

Da Java a scala, e non solo. un assaggio di zucchero sintattico

      Come abbiamo già detto, Scala gira sulla JVM , il codice cioè viene compilato in bytecode java eseguibile normalmente sulla macchina virtuale. Anche se non ben supportata come per la versione java esiste una versione di scala che poggia sul CLR , la macchina virtuale del framework .NET. Detto questo, mettiamo da parte .NET e da ora in poi consideriamo Scala per JVM dove non esplicitamente indicato.
Imparare una nuova sintassi può sembrare noioso e/o una perdita di tempo, la progettazione del linguaggio Scala è veramente molto accurata ed è stata effettuata tenendo in mente un principio ben preciso:
avere un set di regole sintattiche e semantiche uniformi e sempre valide che definiscano il linguaggio
in Scala è difficile trovare casi particolari, eccezioni a una regola o alle convenzioni da dovere ricordare. Scala adotta un modello a Oggetti puro e come vedremo molte delle magie sintattiche del linguaggio non sono altro che convenzioni valide per tutti gli oggetti, compresi quelli definiti dall’ utente. In questo modo un programmatore Scala ha la possibilità di scrivere librerie che è difficile distinguere dal linguaggio nativo con una potenza espressiva senza precedenti.
Cercherò di raccogliere tutte le regole più interessanti segnalandole con (*)



def valoreAssoluto(x: Double): Double = {

if (x >= 0) x

else (-x)

}

Vediamo la definizione di una funzione in Scala, proviamo a definire una funzione di valore assoluto, possiamo notare, dopo la parola chiave def e il nome della funzione, la lista dei parametri tra parentesi tonde, in questo caso il parametro della funzione x:Double e la dichiarazione del tipo di ritorno :Double, dopodiché segue il corpo della funzione tra le parentesi graffe, in cui cerchiamo invano la presenza di return, il compilatore Scala restituisce il valore dell’ ultima espressione corrispondente al tipo di ritorno, nel nostro caso x se x è non negativo e –x in caso contrario, come ci aspettiamo da una funzione valore assoluto. E’ possibile comunque inserire clausole di return esplicite , quando necessario.
Possiamo utilizzare in modo trasparente librerie Java,

import java.math._

def random: Double = java.lang.Math.random()

numeroCasualeTra1e10: Double = random * 10

notiamo che la sintassi per l’import è simile alla corrispondente in Java, il carattere jolly è in questo caso ‘_’ e non ‘*’, la motivazione come vedremo in seguito di questa differenza è da cercare nella possibilità dei nomi di metodi in Scala di contenere caratteri come ‘<’, ‘>’, ‘*’, ecc. , ma non ‘_’ .
nella seconda riga invece vediamo come sia possibile omettere il corpo del metodo tra graffe quando la funzione è costituita da una sola espressione e come sia opzionale indicare le parentesi per le funzioni che non hanno parametri, in questo caso saremo obbligati a richiamare la funzione senza parentesi, se avessimo indicato le parentesi vuote nella definizione della funzione avremmo potuto richiamarla con o senza parentesi.

(*1) – i metodi possono avere nel nome simboli <>-+*... eccetto _
(*2) – un metodo che accetta un solo parametro può utilizzare la notazione infissa.
(*3) – gli operatori sono metodi
(*4) - non esistono tipi primitivi, Int, Double, String, Char, ecc. sono tutte classi della libreria scala

analizziamo il codice

val num = 3 * 8

come abbiamo già visto in precedenza il compilatore inferisce il tipo, ma l' aspetto interessante è che l' operazione di moltiplicazione tra literals non è altro che l'equivalente di

val num = (3).*(8)

Stiamo semplicemente richiamando il metodo * sull' oggetto 3 passando come parametro l'oggetto 8 e restituisce il loro prodotto. Nel primo caso, abbiamo sfruttato la seconda regola e cioè utilizzare il metodo come operatore infisso.

(*5) - quando un metodo viene utilizzato con notazione infissa il metodo viene sempre applicato all' operando a sinistra e ha come parametro l' operando a destra, tranne quando il nome del metodo finisce per ':'. Comprenderemo meglio il perché quando esploreremo le liste in Scala.

domenica 16 ottobre 2011

il primo gradino

Questo blog è un personale block notes nell' esplorazione del linguaggio Scala, uno dei tanti linguaggi di programmazione emersi negli ultimi anni con la peculiarità di unire alle caratteristiche di semplicità, facilità di sintassi dei linguaggi dinamici di scripting come Ruby, Phyton, Groovy alla robustezza dei linguaggi compilati quali Java e C#.
Una meteora o il prossimo protagonista del mainstream ?
Scala sta per  scalable language , un linguaggio progettato per sviluppare applicazioni, appunto, scalabili e cioè in grado di adattarsi a girare in modo trasparente su hardware distribuito e a consumare risorse che aumentano all’ aumentare della richiesta di utilizzo da parte degli utenti, .
Scala è un linguaggio staticamente tipato e multi paradigma, Object Oriented puro e Funzionale, gira su una JVM ed è stato ideato e sviluppato da Martin Odersky, già autore dei Java Generics e del compilatore javac per Java7.
Programmazione Funzionale (FP)? Probabilmente tutti conoscono cosa è la programmazione orientata agli oggetti, e sanno quali sono i suoi vantaggi in termini di gestione della complessità, tra i principali motivi della sua diffusione a partire dagli anni '80, un pò meno diffusa al di fuori di ambienti accademici e nicche di mercato è invece la FP.
La FP è un paradigma di programmazione in cui le funzioni sono entità di prima classe, nella FP l'operazione fondamentale è l'applicazione di funzioni, ogni programma è fondamentalmente una funzione che si applica ai parametri di ingresso e da in output qualcosa, questa funzione è definita in termini di altre funzioni e così via fino ad arrivare a qualche primitiva del linguaggio. La caratteristica fondamentale dei linguaggi funzionali puri è un ossimoro... le variabili sono immutabili o_O. Ebbene si, le variabili in FP non possono avere assegnazioni a parte il momento in cui vengono definite, questo implica che in generale, in un programma funzionale applicando una funzione su un insieme di parametri non ci sarà mai un effetto collaterale sui parametri, l'unico effetto collaterale è la produzione di un risultato, in questo modo valori e variabili possono essere scambiati in qualsiasi momenento e qualsiasi espressione può essere valutata in ogni momento senza possibilità di errori, tale proprietà viene denominata trasparenza referenziale. Un approccio del genere ha un impatto pesante sulla quantità di memoria consumata durante l'esecuzione di un' applicazione FP ed è sicuramente uno dei fattori che in passato hanno limitato l'adozione del paradigma funzionale. Un effetto collaterale della immutabilità delle variabili è che nativamente un programma funzionale non deve occuparsi di tutte le problematiche di acesso concorrente in scrittura e coerenza dei dati tipici rompicapo in ogni applicazione non banale Object Oriented.
Niente paura, Scala non è un linguaggio funzionale puro anche se incoraggia a essere utilizzato come tale, lascia la liberta di utilizzare lo stile classico OO, o meglio lo stile giusto per la necessità contingente.
La sintassi di Scala ha qualcosa di magico, da scoprire un pò alla volta, ma che a prima vista sembra incarnare tutto quello che uno sviluppatore ha sempre desiderato. Unisce i vantaggi dei linguaggi fortemente tipati , come java a la sintassi coincisa dei moderni linguaggi dinamici, questo accade grazie alla inferenza di tipo che il compilatore scala effettua, permettendo una sintassi essenziale e leggibile.

val greet: String ="Hello Scala";
var age = 27
val name = "Maria";

val e var, sono le due modalità per dichiarare le variabili in scala, val precede la dichiarazione delle variabili immutabili in stile FP, var invece è la parola chiave per la dichiarazione di variabili che possono ricevere ri-assegnazioni.
tutte e tre le sintassi dell' esempio sono corrette, l'analizzatore sintattico del compilatore scala quando può inferisce il tipo dal contesto, in java ad esempio avremmo dovuto scrivere

String name = new String("Maria");

come notiamo dal primo esempio , anche in scala la sintassi è molto simile a parte l' assenza della ripetizione di tipo, ma è molto più diretto utilizzare la più coincisa sintassi degli esempi successivi in cui omettiamo completamente il tipo che verrà indovinato dal compilatore a partire dai valori assegnati.
I più pignoli avranno notato l'assenza del punto e virgola dopo la seconda dichiarazione, anche questo è opzionale in scala quando non crea ambiguità.
Un' altro esempio della sintassi coincisa di scala , vediamo una definizione di classe in Java e la corrispondente Scala.

<java>
class Persona{
private int id;
private String nome;
private String indirizzo;
public Persona( int id, String nome, String indirizzo){
this.id=id;
this.nome=nome;
this.indirizzo=indirizzo;
}
}
</java>

<scala>
class Persona (id: int, nome: String, indirizzo: String)
</scala>

il compilatore scala data questa ultima riga di codice genera l' equivalente della classe java precedente compreso un costruttore che accetta in ingresso le proprietà definite per la classe.
Molti accorgimenti sintattici rendono la scrittura di codice Scala molto coincisa e quindi meno soggetta ad errori, ma la caratteristica del linguaggio che aiuta maggiormente a diminuire le righe di codice è sicuramente l'introduzione dei tratti (traits) insieme alla possibilità di estendere il linguaggio con librerie che potranno essere utilizzate come se fossero parte del linguaggio anche per quanto riguarda la sintassi. Tutto questo sarà l' oggetto della nostra indagine nelle prossime puntate.