You are on page 1of 84

Universit degli Studi di Bologna Facolt di Ingegneria Anno Accademico 2010/2011 Capitolo 15 JAVASCRIPT: UN LINGUAGGIO SORPRENDENTE Approccio funzionale

& a oggetti "all in one" Prof. Enrico Denti


DEIS 051.20.93015

enrico.denti@unibo.it

PERCH JAVASCRIPT ?
Perch incorpora molte caratteristiche interessanti, spesso ignote: ben oltre lo scripting per pagine web! Perch adotta segue un approccio funzionale, con funzioni e chiusure concetti fondamentali di computer science qui offerti in forma semplice e pratica Perch adotta un modello object-based senza classi, basato sul concetto di prototipo Perch un linguaggio interpretato, con aspetti dinamici di grande interesse ottima palestra per sperimentare Perch adatto a creare applicazioni multi-paradigma In breve, perch unisce potenza espressiva a un modello computazionale interessante, potente e flessibile !

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

PERCH NON JAVASCRIPT ?


Perch incorpora anche alcuni "errori di giovinezza"
in effetti, passato dalla "non esistenza" all'ampia adozione in (troppo) poco tempo: non ci fu il tempo per "ripulirlo"

Perch soffre qua l di alcune pessime idee


(oltre alle tante ottime idee) la peggiore: un ambiente globale (pure mal definito!) con variabili globali ci pu portare a stili che non rispettano le buone pratiche.. ma che si possono evitare, con adeguate scelte e disciplina

Perch alcune caratteristiche sono controverse


il loose typing riduce le possibilit di intercettare errori di tipo, MA potente, molto flessibile, ed evita i cast
d'altronde, anche lo strong typing non elimina la necessit di testing

l'ereditariet prototype-based concettualmente potente, ma non sempre facile da seguire per chi viene da linguaggi "class based"

1. LE BASI DEL LINGUAGGIO 2. IL LATO FUNZIONALE 3. IL LATO A OGGETTI 4. DOVE I DUE LATI S'INCONTRANO 5. APPLICAZIONI MULTI-PARADIGMA

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

JAVASCRIPT: LE BASI
Nasce come linguaggio di scripting
inventato da Netscape (LiveScript), rinominato in JavaScript nel 1995 dopo un accordo con Sun Microsoft ha la sua variante, JScript (minime differenze) standard: ECMAScript 262

Object-based (ma non object-oriented)


simile a C, Pascal, Java...ma di Java ha "piuttosto poco": anzi, assomiglia pi ai linguaggi funzionali come Lisp

Come si usa:
i programmi Javascript si includono di solito nel sorgente HTML delle pagine Web e sono eseguiti nel browser Javascript Virtual Machine: Rhino, SpiderMonkey (Mozilla: v. anche TraceMonkey), V8 (Google), JScript (Microsoft)
V8 (C++), Rhino (Java), SpiderMonkey (C) sono usabili anche stand alone

Inserimento nelle pagine web


<HTML> <HEAD> <TITLE>...</TITLE> </HEAD> <BODY> ... <SCRIPT Language="JavaScript"> <!-- Per non confondere i vecchi browser
IL PROGRAMMA JAVASCRIPT
// Commento Javascript per non confondere browser

-->

</SCRIPT> </BODY> </HTML> Una pagina HTML pu contenere pi tag <script>.

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

Elementi linguistici: stringhe


Il tipo string denota stringhe di caratteri Unicode
non esiste il tipo char: un carattere una stringa lunga 1 ogni stringa un oggetto IMMUTABILE dotato di propriet, tra cui length, e di metodi, tra cui substring (indici first, last come in Java last indica il primo escluso)

Le costanti stringa possono essere delimitate sia da virgolette sia da apici singoli
se occorre annidare virgolette e apici, occorre alternarli: document.write('<IMG src="image.gif">') document.write("<IMG src='image.gif'>")

Le stringhe si possono concatenare con l'operatore +


ma attenzione in presenza di numeri: document.write(beta + alfa + 2) document.write(beta + (alfa + 2))

Elementi linguistici: numeri


Il tipo number denota un reale a 64 bit
non ci sono interi, quindi non esistono problemi di overflow o errori legati a conversioni numeriche la divisione quindi sempre fra reali, anche con operandi apparentemente interi (esempio: 7/3 = 2.33333) gli operatori bitwise operano su interi ottenuti convertendo un reale sono lentissimi e inefficienti: evitare!

Le costanti numeriche sono sequenze di caratteri numerici non racchiuse da virgolette o apici
la costante NaN rappresenta il "risultato" di operazioni impossibili: non uguale ad alcun valore, incluso se stessa un'operazione che coinvolga un NaN d come risultato NaN la costante Infinity rappresenta un valore maggiore del massimo reale positivo rappresentabile (1.79 * 10+308)

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

Elementi linguistici: altri


Le costanti booleane sono true e false Il loro tipo boolean
ma attenzione alla valutazione delle condizioni nelle strutture di controllo (v. oltre)

Altre costanti sono null e undefined


undefined indica un valore indefinito

I commenti hanno le classiche forme:


a riga singola su pi righe // commento su riga singola /* commento su pi righe */

OCCHIO a commentare blocchi di codice con espressioni regolari: in esse possono comparire stringhe come "*/"

Espressioni
Sono consentite sostanzialmente le stesse espressioni lecite in Java
espressioni numeriche: somma, sottrazione, prodotto, divisione (sempre fra reali), modulo, shift, espressioni condizionali con ? : espressioni stringa: concatenazione con + espressioni di assegnamento: con = (e sue varianti)

Esempi:
document.write(18/4) // fra reali document.write(18%2) document.write("paolino" + 'paperino')

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

Variabili
Le variabili in Javascript sono loosely typed possibile assegnare alla stessa variabile prima un valore di un tipo, poi un valore di un altro tipo! Ad esempio: alfa = 19 beta = "paperino" alfa = "zio paperone" // tipo diverso!! Sono consentiti incrementi, decrementi e operatori di assegnamento estesi (++, --, +=, )

Dichiarazione implicita o esplicita


La dichiarazione di una variabile pu essere implicita (la si usa e basta) o esplicita (con la parola chiave var) Esempio: var pippo = 19 pluto = 18 // dichiarazione esplicita // dichiarazione implicita

La dichiarazione implicita introduce sempre e solo variabili globali La dichiarazione esplicita pu introdurre variabili globali o locali, a seconda di dove compare.

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

Tipo dinamico
Il tipo (dinamico) di una espressione (variabili incluse) ottenibile tramite l'operatore typeof : typeof(18/4) d number typeof("aaa") d string typeof(false) d boolean typeof(document) d object typeof(document.write) d function
anche se talvolta typeof restituisce risultati inattesi: ad esempio, typeof([1,2,3]) d object anzich array

Se usato su variabili, typeof restituisce il tipo attuale della variabile (o dell'oggetto): a = 18; typeof(a) d number a = "ciao"; typeof(a) d string

Istruzioni
Le istruzioni devono essere separate una dall'altra o da un fine riga, o da un punto e virgola (similmente al Pascal e diversamente dal C o da Java) Esempio:
alfa = 19 // fine riga beta = "paperino" ; gamma = true document.write(beta + alfa)

ATTENZIONE alle conseguenze: ("semicolon insertion") se si va a capo dove non si deve Esempio:
return 18; return 18; // restituisce 18 // restituisce undefined // (statement irraggiungibile)

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

Strutture di controllo
Javascript dispone delle usuali strutture di controllo if, switch, for, while, do/while pi due specifiche per operare su oggetti: for in e with ATTENZIONE: il for..in di JavaScript non equivalente al foreach di Java/C# poich itera sui nomi degli elementi della collezione, non sugli elementi stessi
per gli array, i nomi degli elementi sono gli indici (0,1,2) una caratteristica utile per gli array associativi, che per pu risultare fuorviante se sconosciuta o inattesa il for classico
v = [13,24,37]; for (i=0; i < v.length; i++) document.write( v[i] ); // stampa 13,24,37

il forin
v = [13,24,37]; for ( item in v ) document.write( item ); // stampa 0,1,2

Operatori relazionali
Le operatori relazionali sono i soliti (==, !=, >, <, >=, <=) pi due nuovi (===, !==), combinabili con gli usuali operatori logici AND (&&), OR (||), NOT (!)
ATTENZIONE: nella valutazione di condizioni si considera falso non solo false, ma ogni valore falsy ovvero anche null, undefined, la stringa vuota (''), il valore 0 ed NaN Ogni altro oggetto, inclusa la stringa 'false', vero.

I due operatori === e !== offrono una alternativa pi sensata al comportamento discutibile dei due ==, !=

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

Operatori di uguaglianza: "the good bros" vs "the evil bros" (1)


Se i tipi dei due operandi sono diversi, == e != applicano type coercion secondo regole molto discutibili, che sconfinano nell'incoerenza:
0 == '' true, perch sono entrambi falsy values (eeh?!) 0 == '0' true, perch.. 0 coercibile a '0' (eh?! auguri) false == 'false' false, come giusto che sia, MA false == '0' true, perch sono due falsy values (aargh!) false == undefined false (ok, accettabile) false == null false (ok, accettabile) MA null == undefined true fulgido esempio di coerenza!! La perla finale (anche con sottoparti della stringa): ' \t\r\n' == 0 true perch.. erano ubriachi.

Operatori di uguaglianza: "the good bros" vs "the evil bros" (2)


Viceversa === e !== NON applicano MAI alcuna type coercion, risultando quindi pi affidabili e predicibili CONSIGLIO: dimenticarsi dei due "evil brothers", preferendo i pi educati "good brothers"
Riferimento: Douglas Crockford, "Javascript: The Good Parts", O'Reilly, 2008

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

1. LE BASI DEL LINGUAGGIO 2. IL LATO FUNZIONALE 3. IL LATO A OGGETTI 4. DOVE I DUE LATI S'INCONTRANO 5. APPLICAZIONI MULTI-PARADIGMA

Definizione di Funzioni
Le funzioni sono definite dalla keyword function
il nome opzionale: JavaScript ammette anche funzioni anonime, di fatto analoghe alle lambda-expression c' una sottile differenza fra function expression e function declaration la vedremo meglio pi avanti

Possono essere sia funzioni in senso proprio, sia procedure che non restituiscono nulla
ma non si usa la keyword void

I parametri formali sono senza dichiarazione di tipo


function sum(a,b) { return a+b } function printSum(a,b) { document.write(a+b) } funzione procedura (non ha istruzione return)

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

10

Invocazione di funzioni
Le funzioni sono invocate nel solito modo, tramite l'operatore di chiamata (), fornendo i parametri attuali:
result = sum(18,-3);

Se i tipi non hanno senso per le operazioni fornite, l'interprete Javascript d errore a runtime

Passaggio dei parametri


I parametri attuali possono non corrispondere in numero ai parametri formali previsti:
se i parametri attuali sono pi di quelli necessari, quelli extra sono ignorati se i parametri attuali sono meno di quelli necessari, quelli mancanti sono inizializzati ad undefined

Il passaggio dei parametri avviene per valore


nel caso di oggetti, ovviamente si copiano riferimenti

A differenza di C e Java, si pu definire una funzione dentro un'altra molteplicit di ambienti definiti e in essere a run-time concetto di chiusura (v.oltre)

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

11

Variabili e Scope
Lo scope delle variabili in Javascript : globale, per quelle definite fuori da funzioni e per quelle definite dentro a funzioni in modo implicito locale, per quelle definite dentro a funzioni in modo esplicito (ivi compresi i parametri passati) ATTENZIONE: un blocco NON delimita uno scope ! le variabili definite dentro a blocchi innestati sono comunque riferite all'ambiente che le contiene
x = '3' + 2; // la stringa '32' { { x = 5 } // blocco interno y = x + 3; // x denota 5, non "32" }

Esperimento
x = '3' + 2; // '32' { { x = 5 } // blocco y = x + 3; // x = 5 }

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

12

Variabili: dichiarazione esplicita


Fuori da funzioni, la parola chiave var ininfluente: la variabile definita comunque globale. All'interno di funzioni, invece, la parola chiave var indica che la nuova variabile ha come scope la funzione
ossia, locale a tale funzione, non globale come tale, non visibile fuori da quella funzione ergo, cessa di esistere quando l'ambiente della funzione scompare (gi.. ma quando scompare? ne discuteremo..)

Vediamo due primi, semplici esperimenti con:


una sola funzione, che definisce una variabile locale l'ambiente globale (che c' sempre), dove viene usata una variabile con quel nome.. con risultati variegati.

Esperimento 1

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

13

Esperimento 2

globale

locale
f locale = 4

ff globale = 4 globale =4

f globale = 3

Ambienti di definizione innestati


Catena lessicale vs. Catena dinamica
La possibilit di definire funzioni dentro altre funzioni apre una prospettiva inesistente in C e Java
ma ben nota in Pascal..

In presenza di funzioni definite dentro altre funzioni, infatti, si crea una catena di ambienti di definizione innestati uno dentro l'altro: la catena lessicale A runtime, l'attivazione delle varie funzioni d poi luogo a una sequenza di ambienti attivi istante per istante, non necessariamente uguale alla precedente: la catena dinamica Il mondo si complica: DOVE vanno cercate le definizioni delle variabili (e delle funzioni..) via via utilizzate?

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

14

Il problema
Quando si usa un simbolo non definito in un certo ambiente, DOVE va cercata una definizione per esso? Ci sono due possibilit: cercarla nell'ambiente in cui la funzione fisicamente definita, seguendo quindi la catena lessicale cercarla nell'ambiente attivo al momento della chiamata, seguendo quindi la catena dinamica Le due scelte corrispondono ad adottare due modelli computazionali diversi. Per capire il problema, si consideri l'esempio seguente.

Esperimento 3
Si consideri il seguente programma Javascript:
var x = 20; function provaEnv(z) { return z + x; } alert(provaEnv(18)) // visualizza certamente 38 function testEnv() { var x = -1; return provaEnv(18); // COSA CALCOLA ? QUALE x USA? }

Nella funzione testEnv si ridefinisce il simbolo x, poi si invoca provaEnv, che usa il simbolo ma QUALE x ? il simbolo x nell'ambiente in cui provaEnv definita ha infatti un significato diverso rispetto a quello che ha nell'ambiente in cui provaEnv chiamata!

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

15

Il nocciolo della questione (1)


L'esperimento qui mostrato in JavaScript per comodit, ma di valenza assolutamente generale evidenzia la questione: provaEnv non definisce x, ma la usa: quindi, occorre cercare altrove una definizione per essa nell'ambiente in cui provaEnv definita, che quello globale, esiste una definizione per x in cui x vale 20 tuttavia, a run-time non questa la sequenza delle chiamate: provaEnv infatti chiamata da testEnv, nel cui ambiente esiste una DIVERSA definizione per x in cui x vale -1 Quale x si dovrebbe considerare? Cosa "sarebbe giusto" che accadesse?

Il nocciolo della questione (2)


Non esiste una risposta "giusta in assoluto": potenzialmente, entrambe le scelte sono lecite, ed entrambe hanno pro e contro se si sceglie di considerare l'ambiente di definizione lessicale, si dice che il modello computazionale adotta la CHIUSURA LESSICALE
in tal caso, x varr 20 e la funzione restituir 38

se si sceglie invece di considerare l'ambiente di definizione dinamico, si dice che il modello computazionale adotta la CHIUSURA DINAMICA
in tal caso, x varr -1 e la funzione restituir 17

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

16

Il nocciolo della questione (3)


La CHIUSURA LESSICALE vincola a priori un simbolo a un certo legame, quindi meno dinamica.. ma proprio per questo permette di "leggere" un programma senza dover ricostruire la sequenza delle chiamate Al contrario, la CHIUSURA DINAMICA consente la massima dinamicit, ma al prezzo di dover "simulare mentalmente" la sequenza delle chiamate per capire i legami dei simboli e quindi "cosa fa" il programma Per questo motivo, la gran parte dei linguaggi adotta la chiusura lessicale JavaScript incluso
La funzione del nostro esperimento restituir perci 38.

Esperimento 3 (segue)

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

17

Chiusure
In generale, per chiusura si intende una funzione con variabili libere, che vengono legate in un ambiente
origine: linguaggi funzionali come Lisp, Scheme (~1960) dove si trovano: tipicamente sono disponibili in linguaggi in cui le funzioni sono entit di prima classe (ossia passabili come argomenti, restituite da funzioni, assegnate a variabili, etc)

Il tempo di vita di tali variabili pari almeno a quello della chiusura, anche se la funzione termina prima
in linguaggi SENZA chiusure, il tempo di vita di una variabile locale coincide con quello della funzione che la contiene in linguaggi CON chiusure, invece, una variabile locale pu sopravvivere oltre la funzione che la contiene e definisce, se referenziata da un'altra funzione interna che ne fa uso. (tipicamente si fa uso di un garbage collector per ripulire)

Funzioni e chiusure in JavaScript


Javascript offre funzioni come entit di prima classe
una funzione una entit manipolabile come ogni altro dato pu essere assegnata a variabili, definita e usata "al volo", passata come argomento, restituita da una funzione, etc pu avere un nome o anche non averlo (lambda-expression)
il nome di funzione attribuito in una function expression ha scope limitato all'interno della funzione (scarsamente utile, se non per chiamate ricorsive)

var f = function g(x){ return x/10; } g(32) // ERRORE il nome g indefinito


il nome di funzione attribuito in una function declaration ha invece scope pari all'ambiente di definizione

function g(x){ return x/10; } g(32) // OK il nome g noto nell'ambiente

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

18

Funzioni e chiusure in JavaScript (1)


Javascript offre funzioni come entit di prima classe
una funzione una entit manipolabile come ogni altro dato pu essere assegnata a variabili, "definita e usata al volo", passata come argomento, restituita da una funzione, etc Esempi: var f1 = function (z) { return z*z; } // senza nome var f2 = function g(x){ return x/10; } // con nome var f3 = f2; // aliasing var r1 = f1(7); // 49 (non chiamabile in altro modo) var r2 = f2(36); // 3.6 (o anche f3(36) ma non g(36)) var r3 = function(y){return y+1;}(8); // 9
// funzione definita e usata "al volo" una sola volta

Funzioni e chiusure in JavaScript (2)


Javascript offre funzioni come entit di prima classe
una funzione una entit manipolabile come ogni altro dato pu essere assegnata a variabili, "definita e usata al volo", passata come argomento, restituita da una funzione, etc Esempi: function calc(f, x) { return f(x); } // con nome
// funzione del 2 ordine: calcola la f passatale var r4 = calc(Math.sin, .8) // 0.7173560908995228 var r5 = calc(Math.log, .8) // -0.2231435513142097 var r6 = calc(f1, .8) // 0.6400000000000001 var r7 = calc(f2, .8) // 0.08 var r8 = calc(function(r){return r*r*Math.PI}, .8) // 2.0106192982974678

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

19

Funzioni e chiusure in JavaScript (2)


Javascript offre funzioni come entit di prima classe
una funzione una entit manipolabile come ogni altro dato pu essere assegnata a variabili, "definita e usata al volo", passata come argomento, restituita da una funzione, etc Esempi: ATTENZIONE: l'argomento attuale function calc(f, x) { vera funzione, non // con nome dev'essere una return f(x); } // funzione del 2 ordine: calcola la f passatale una stringa o una espressione! var e1 = calc("Math.sin", .8) // ERRORE var e2 = calc( x*x+1 , .8) // ERRORE QUINDI, non si pu in questo modo eseguire una funzione ad esempio letta da tastiera ma si pu farlo in altra maniera (che vedremo fra poco)

Funzioni e chiusure in JavaScript (3)


Nasce una chiusura quando una funzione innestata in un'altra fa riferimento a variabili della funzione esterna
l'entit-chiusura nasce a runtime all'atto della chiamata della funzione pi esterna, ma sopravvive al suo termine, perch necessario mantenere vive le variabili ancora referenziate (che serviranno all'esecuzione successiva) Esempi: function ff(f, x) { // CREA UNA CHIUSURA return function(r){return f(x)+r;} }
// f e x sopravvivono al termine dell'esecuzione di ff var r9 = ff(Math.sin, .8)(3) // 3.7173560908995228

var r10 = ff( function(q){return q*q}, .8 )(0.36)


// 0.64 + 0.36 = 1 // come si vede, f e x esistono ancora! // altrimenti, ff non potrebbe calcolare

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

20

Funzioni e chiusure in JavaScript (4)


Una chiusura un modo efficace per rappresentare uno stato privato e nascosto
infatti, le variabili mantenute vive dalla chiusura restano comunque visibili solo entro la funzione "esterna" e dunque invisibili fuori ! naturalmente, ci rende tali funzioni "non pi pure", dato che il loro comportamento dipende dallo stato memoization

Una chiusura permette una comunicazione "nascosta"


infatti, se si definiscono pi funzioni sulla stessa chiusura, esse condivideranno uno stato nascosto, usabile per comunicare.

Una chiusura permette di costruire strutture di controllo


infatti, intorno alla funzione ricevuta come argomento si pu costruire ci che si vuole (if, while,.. o qualunque altra cosa!)

Funzioni e chiusure in JavaScript (5)


Una chiusura un modo efficace per rappresentare uno stato privato e nascosto
Esempio: function inc2(x) { // Chiusura con stato x nascosto return function(){ return x+=2;} } var prossimoNumeroPari = inc2(0) var prossimoNumeroDispari = inc2(1) do{ // ovviamente do/while fa sempre un giro in pi.. var p = prossimoNumeroPari(); write(p); } while(p<16); // 2 4 6 8 10 12 14 16 do{ // ovviamente do/while fa sempre un giro in pi.. var d = prossimoNumeroDispari(); write(d); } while(d<21); // 3 5 7 9 11 13 15 17 19 21

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

21

Funzioni e chiusure in JavaScript (6)


Una chiusura permette una comunicazione "nascosta"
Esempio: function myenv() {// Chiusura con stato msg nascosto var msg = "bla bla"; return [ function(x) { msg = x; }, function() { return msg; } ];
// crea e restituisce un array di due funzioni // che condividono fra loro lo stato nascosto msg

} var arrayDiDueFunzioni = myenv(); var s1 = arrayDiDueFunzioni[1](); // "bla bla" arrayDiDueFunzioni[0]("buuh"); var s2 = arrayDiDueFunzioni[1](); // "buuh"

Funzioni e chiusure in JavaScript (7)


Una chiusura permette di costruire strutture di controllo
Esempio: function loop(statement, n) {// Chiusura var k = 0; return function iter() { if (k<n) { k++; statement(); iter(); } } }
// ESEMPIO DI ITERAZIONE: ripete 10 volte 'i++'

i = 1; loop( function(){ i++ }, 10)(); document.write(i); // 11 In questo modo ci si possono inventare strutture di controllo qualunque (ad esempio, cicli a passo step, o altro..)

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

22

Funzioni e chiusure in JavaScript (8)


Una chiusura, per, va scritta con cura.. facendo attenzione se si creano funzioni dentro a un ciclo
Esempio: si vorrebbe creare un array di funzioni che, quando chiamate, restituiscano numeri identificativi diversi (ad es. 4,5,6) function test(list){ var k=0; for (var i = 4; i < 7; i++) list[k++] = function(){ return i;}; } in realt, queste funzioni restituiranno tutte il valore che avr i quando saranno chiamate, ovvero sempre 7, poich fanno tutte riferimento all'unica i presente nella chiusura !
var array = []; test(array); for(var j=0; j<array.length; j++) println(array[j]()); // 7 7 7 !!!

Funzioni e chiusure in JavaScript (8)


Una chiusura, per, va scritta con cura.. facendo attenzione se si creano funzioni dentro a un ciclo
Esempio: per ottenere il comportamento corretto occorre separare le variabili nella chiusura in JavaScript, questo richiede necessariamente una funzione accessoria, dato che i blocchi non definiscono uno scope: function test(list){ var k=0; function aux(j){ return function(){ return j;}} for (var i = 4; i < 7; i++) list[k++] = aux(i); } ora funziona (le tre invocazioni restituiscono 4,5,6), perch la funzione aux crea, col suo parametro j, tre diverse copie di i

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

23

Chiusure nei linguaggi OO


La chiusura un concetto molto potente
nato dai linguaggi funzionali (Lisp, Scheme), ripreso nei successivi (Eiffel, Smalltalk, Erlang), poi da JavaScript (Ruby, Perl) sempre pi presente nei linguaggi mainstream, o in forma completa o nella forma limitata di costrutti "closure-like" nei linguaggi OO, una chiusura diventa un modo per incapsulare comportamento [come in un oggetto], portarselo in giro [come in un oggetto], ma avendo accesso al contesto in cui fu definito

Oggi presente in C#, Scala e prossimamente Java, C++


C#: offre gi chiusure via metodi anonimi e lambda expression Java: chiusure previste in Java 8 (autunno 2012) (per ora, solo supporto parziale, via classi anonime e inner classes) C++: per ora, supporto solo parziale (overload di operator() senza variabili locali), ma di fatto ci sono due proposte molto simili fra loro, gi supportate sia in Visual C++ 2010 sia in gcc-4.5

Pseudo-chiusure in Java 6 (1)


Attualmente (Java 6), Java non supporta vere chiusure Alcune funzionalit possono per essere emulate tramite classi anonime e nested classes
ATTENZIONE: le nested classes di cui si parla (inner classes) non c'entrano nulla con le "static inner classes" gi esistenti

Tali emulazioni sono tuttavia insoddisfacenti


codice verboso e ridondante operatori di controllo di flusso solo locali problemi semantici sottili con return, break, continue, this

Un migliore supporto atteso in Java 8


"project lambda" notazione molto simile a quella di C#

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

24

Pseudo-chiusure in Java 6 (2)


1. Emulazione tramite Classi anonime in un metodo
Definendo una classe anonima in un METODO, si pu: accedere in lettura e scrittura al contesto lessicale della classe che la contiene accedere in sola lettura al contesto lessicale del metodo che la contiene mentre non si pu: accedere in scrittura al contesto lessicale del metodo che la contiene (ossia, cambiare il valore delle variabili) Per garantire il rispetto di tale vincolo, Java 6 consente l'accesso al contesto lessicale del metodo solo per variabili final possibile trucco (imperfetto e parziale): usare un riferimento immutabile (final) a un contenitore in cui mettere il vero contenuto (ad esempio, un array di un solo elemento)

Pseudo-chiusure in Java 6 (3)


Classi anonime in un metodo: Esempio
class Pippo { private int result; Non pu cambiare arg

public void calc(final int arg){ new Thread(new Runnable(){ public void run(){ result += 2; // accede al contesto lessicale della classe che la contiene result += arg; // accede in sola lettura al contesto lessicale del
// metodo che la contiene (solo variabili final)

}}).start(); }

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

25

Pseudo-chiusure in Java 6 (4)


2. Emulazione tramite Nested classes
Definendo una nested class in un'altra classe, si pu accedere a ogni variabile di ogni istanza della classe esterna, creando cos qualcosa di molto simile a una vera chiusura MA al prezzo di difficolt sintattiche nella creazione degli oggetti della nested class, che richiedono una notazione particolare operatore new applicato all'istanza della classe esterna (!) con ci perdendo il vero valore delle chiusure, che dato dall'insieme "concisione + leggibilit + eleganza".

Pseudo-chiusure in Java 6 (5)


Nested classes: Esempio
class Esterna { private int state; NON CONFONDERE con le static inner classes che qui avrebbero un modificatore static

class Interna { public int incr() { return state++; } } ...

public static void main(String[] args){ sintassi innaturale Esterna esterna = new Esterna(); esterna.Interna interna = esterna.new Interna(); // accede a ogni variabile di ogni istanza della classe esterna for(int i = esterna.state; (i = interna.incr() ) < 5; ) System.out.println(i); } }

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

26

Chiusure in C# e ~Java8 (1)


Idea: lambda expressions con chiusura lessicale C#: nuova notazione: { params => body } Java 8: probabile notazione: #{ params -> body }
params una lista di parametri formali con tipo, separati da virgole; pu mancare nel caso di funzioni senza argomenti body una lista di istruzioni, di cui l'ultima pu essere una espressione (che denota il valore dell'intera chiusura). SEMANTICA INFORMALE: l'esecuzione della chiusura crea un oggetto contenente il codice del body + il contesto lessicale

Alcuni esempi in C#...


{ { { { int x => x+1 } int x, int y => x+y } string s => int.parse(s) } => Console.WriteLine("hello")}

.e in Java8 (expected):
{ { { { int x -> x+1 } int x, int y -> x+y } -> e+55 } o anche solo: { e+55 } System.out.println("hello") }

Chiusure in C# e ~Java8 (2)


Come in ogni chiusura, il contesto lessicale:
include le variabili libere referenziate dal body (incluso this) abbandonato se body esegue una return, che causa l'uscita dal metodo che sta eseguendo la chiusura

Vantaggi:
ci si libera del codice "verboso" tipico delle classi anonime si possono alleggerire molti costrutti e pattern di largo uso
in particolare si mira ad alleggerire i "vertical problems" (5-6 linee di codice solo per incapsulare una singola istruzione) evitando per di trasformarli in "horizontal problems" (singole righe di codice troppo lunghe e quindi incomprensibili per diverso motivo) riducendo al contempo l' "horizonal noise" (sintassi sovrabbondante).

utile integrazione con delegati (C#) e method references (Java8) nonch con extension methods (C#, Java8)

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

27

Delegati (C#) e Method references (Java8)


Un method reference (Java8) qualcosa di molto simile a un delegato C#
in C#, un delegato incapsula un puntatore a funzione, ma non ha una sintassi "lambdalike" in Java8, un method reference pu essere visto come uno SHORTCUT LINGUISTICO per una lambda expression, in cui al posto del body c' un riferimento a un metodo gi esistente Sintassi C#: delegate Sintassi Java8: #nomeTarget.nomeMetodo

Come in un delegato C#, il metodo referenziato da un method reference pu essere statico o di istanza
nel primo caso, basta il metodo stesso nel secondo, occorre specificare anche l'istanza associata

Method references (Java8)


Riferimenti a metodi statici
SINTASSI: #nomeClasse.nomeMetodo ESEMPIO: Arrays.sort(myvector,#Point.sortByX); dove myvector un array di Point e quest'ultima definisce:
public static int sortByX(Point p1, Point p2){...}

LAMBDA EXPRESSION ASSOCIATA:


lista degli argomenti: come nel metodo sortByX della classe Point in questo caso Point,Point body: chiama quello di sortByX della classe Point conversione SAM: convertita nel tipo equivalente Comparator<Point> in quanto il tipo risultante nel secondo argomento di Arrays.sort NB: in caso di ambiguit dovuta a presenza di pi metodi overloaded, si possono fornire nel method reference anche i tipi dei parametri in questo caso, ad esempio, scrivendo #Point.sortByX(Point,Point)

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

28

Method references (Java8)


Riferimenti a metodi di istanza
SINTASSI: #nomeIstanza.nomeMetodo ESEMPIO: Arrays.sort(myvector,#mysorter.sortByX); LAMBDA EXPRESSION ASSOCIATA:
lista degli argomenti: come nel metodo sortByX della classe Point in questo caso Point,Point body: chiama sortByX su una FINAL COPY dell'istanza mysorter

ATTENZIONE: work in progress!


non stato ancora stabilito quali restrizioni saranno introdotte
per piuttosto probabile che NON saranno ammesse espressioni generali della forma #metodo1(argomento).metodo2

non stata ancora stabilita la sintassi definitiva


possibile la scelta del simbolo '#' come operatore infisso al posto del '. ' ovvero, nel caso: mysorter#sortByX

Extension Methods (1)


Gli extension methods (C#, Java8) sono un mezzo per estendere classi scrivendo i nuovi metodi altrove
utile quando non si ha il codice della classe da estendere o non si pu cambiarlo (classi final / sealed)

Idea: scrivere i metodi "aggiuntivi" altrove, come metodi statici di un'altra classe, ma etichettandoli in modo da poterli usare "logicamente" come metodi standard Per farlo, si stabilisce che:
il PRIMO ARGOMENTO di tali metodi sia l'oggetto target dell'invocazione e indichi col suo tipo la classe da estendere, di cui cio il metodo esteso andr a fare logicamente parte SINTASSI C#: primo argomento etichettato con this SINTASSI Java8: primo argomento senza qualifiche particolari

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

29

Extension Methods (2)


C#
static class MyExtensions { public static string getValueAsString(this Counter c) { return "" + c.getValue() } }

cliente
Counter c = new Counter(7); int val = c.getValue(); // ovvio // EXTENSION METHOD! string s = c.getValueAsString();

Java8
class MyExtensions { public static String getValueAsString(Counter c) { return "" + c.getValue() } }

Extension Methods (3)


Gli extension methods si sposano particolarmente bene con le lambda expressions
come caso particolare, infatti, gli extension methods possono estendere interfacce, fornendo loro una implementazione di default di uno o pi metodi e poich le lambda sono convertite (sia da C# sia da Java8, seppur in modo diverso) in un qualche tipo di interfaccia..

Gli extension methods consentono di riscrivere pattern di largo uso in modo particolarmente efficace e conciso
purch naturalmente le API fondamentali (in primis: JCF) siano adeguate di conseguenza, per sfruttare le nuove possibilit

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

30

Chiusure in Java8: Esempio 1


Eliminazione delle classi anonime (vertical problem)
adesso (Java 6) class Pippo { private int result; public void calc(final int arg){ new Thread(new Runnable(){ public void run(){ result += 2; // accede al contesto lessicale della classe che la contiene result += arg; // accede in sola lettura al contesto lessicale del
// metodo che la contiene (solo variabili final)

}}).start(); }

in futuro (Java 8)
new Thread(#(){ result += 2; resutl += arg; }).start(); }
// accede al contesto lessicale della classe che la contiene // accede in sola lettura al contesto lessicale del metodo // che la contiene (solo variabili final)

NB: continua a non poter cambiare arg

Chiusure in Java8: Esempio 2


Dalla situazione attuale a.. quello che sar:
adesso (Java 6) Collections.sort(mylist, new Comparator<Point> ){ public int compare(Point p1, Point p2){ // vertical problem
return p1.getX()<p2.getX() ? -1 : (p1.getX()>p2.getX() ? 1 : 0);

}}); in futuro (Java 8) PASSO 1 Collections.sort(mylist, #{ Point p1, Point p2 -> // horizontal problem p1.getX()<p2.getX() ? -1 : (p1.getX()>p2.getX() ? 1 : 0) } ); in futuro (Java 8) PASSO 2 (con librerie rivisitate) Collections.sortBy(mylist, #{ Point p -> p.getX() } ); dove sortBy prevede come secondo argomento non pi un Comparator, ma soltanto un Extractor che si limiti a estrarre una chiave di confronto (Comparable). in futuro (Java 8) PASSO 3 (con librerie rivisitate e method reference) Collections.sortBy(mylist, #Point.getX ); in futuro (Java 8) PASSO 4 (con extension methods) mylist.sortBy( #Point.getX ); // o forse Point#getX

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

31

Chiusure in C# e ~Java8: cenni implementativi


Sia C# sia Java 8 supportano le chiusure convertendole in qualche genere di codice equivalente
in C#, il codice equivalente si basa sul delegato "naturale" per quella espressione (+ una interfaccia + invoke): ad esempio,
{ int x, int y => x+y } { => Console.WriteLine("..")} tipo: { int,int => int } tipo: { => void }

in Java8, il codice equivalente si basa su una classe anonima derivata dall'interfaccia SAM (Single Abstract Method) associata all'espressione lambda

Conseguentemente:
in C#, le lambda expression sono assimilate a normali interfacce, con piena interoperabilit con tutte le API esistenti in Java8, le lambda expression sono gestite diversamente e ristrette a situazioni specifiche (SAM-convertible contexts), ma non per questo sono meno potenti.

Chiusure in Java8: come, dove, quando


Come avviene la conversione in lambda / SAM ?
Ammesso che esista una interfaccia SAM: public interface CallbackHandler { public void callback(Context c); } un riferimento a essa costituisce un tipo lecito per la lambda expression corrispondente: CallbackHandler ch = #{ Context c -> System.out.println("hi!") } in quanto tale lambda expression prende in ingresso un argomento Context, ha tipo di ritorno void e non lancia eccezioni.

Dove pu apparire una lambda expression in Java8?


SOLO dove pu essere convertita nel tipo-SAM corrispondente OVVERO in assegnamenti, cast, invocazioni di metodi

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

32

Chiusure in Java8: come, dove, quando


Perch si possono referenziare variabili locali solo final?
Perch le variabili locali "non final" sono tipicamente usate in idiomi sequenziali, dove permettere di referenziarle rischierebbe di creare facilmente corse critiche, creando solo nuovi problemi. Esempio di idioma NON permesso: int sum = 0; mylist.foreach(#{ elem -> sum+= elem.size(); }); a meno di non vincolare il multi-threading, difficile scrivere bodies che non abbiano corse critiche a qualche livello

necessaria la qualifica esplicita final ?


attualmente (Java 6) s, ma in Java 8 NO perch il compilatore effettuer una forma di type inference: conter la sostanza
il compilatore accetter variabili non formalmente final che per siano effettivamente final, ovvero in cui l'aggiunta della qualifica final non causi errore di compilazione.

MODELLI COMPUTAZIONALI PER LA VALUTAZIONE DI FUNZIONI


Ogni linguaggio che preveda funzioni deve prevedere un modello computazionale per la loro valutazione. Tale modello deve in particolare stabilire:
QUANDO si valutano i parametri COSA si passa alla funzione COME si attiva la funzione (si cede il controllo ?)

Tradizionalmente, il pi usato il modello applicativo:


QUANDO si valutano i parametri: subito, prima di ogni altra cosa COSA si passa alla funzione: di solito, il valore dei parametri [a volte, per certi tipi o a esplicita richiesta: il loro indirizzo] COME si attiva la funzione: tipicamente, chiamandola e cedendo il controllo [modello sincrono si attende che essa termini]

Tuttavia, esso ha alcuni difetti.

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

33

MODELLO APPLICATIVO: VANTAGGI E LIMITI


Il modello applicativo molto usato perch semplice e nel complesso efficiente
valuta i parametri una sola volta per ogni chiamata passa dei valori (entit tipicamente snelle e semplici da gestire) o al limite indirizzi (entit egualmente snelle e semplici) rende facile seguire il flusso di esecuzione (leggibilit)

Tuttavia, valutando i parametri a priori pu causare inefficienze e soprattutto fallimenti non necessari
infatti, valutando i parametri sempre e comunque, pu fare lavoro inutile se alcuni di essi non risultano poi necessari in quella chiamata ma soprattutto, se la valutazione di uno di tali parametri d errore (o eccezione), questo modello causa un fallimento che si sarebbe potuto evitare con un approccio diverso.

MODELLO APPLICATIVO: ESEMPI DI FALLIMENTI EVITABILI


Esempio in Java
class Esempio { static void printOne(boolean cond, double a, double b){ if (cond) System.out.println("result = " + a); else System.out.println("result = " + b); } public static void main(String[] args) { int x=5, y=4; printOne(x>y, 3+1, 3/0); //ArithmeticException: / by zero } }

In questo esempio, la valutazione dei parametri attuali causa eccezione, ma in realt in questa chiamata il parametro b non sarebbe stato usato perch x>y vera fallimento evitabile! Con un diverso modello computazionale, questa invocazione avrebbe potuto avere successo!

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

34

MODELLO APPLICATIVO: ESEMPI DI FALLIMENTI EVITABILI


Esempio in JavaScript
var f = function(flag,x) { return (flag<10) ? 5 : x; } var b = f(3, abort() ); // Errore!! document.write("result =" + b);

Di nuovo, il modello computazionale applicativo causa un fallimento non necessario, in quanto valuta tutti i parametri a prescindere dalla loro reale utilit nella chiamata corrente In questo caso, infatti, flag<10 vera (perch il valore passato per flag 3) e dunque la funzione non avrebbe mai usato x Di nuovo, con un diverso modello computazionale questa chiamata avrebbe potuto avere successo!

IL MODELLO CALL-BY-NAME
Il modello applicativo non il solo possibile: in particolare, il "modello normale" (introdotto da Algol60) adotta un meccanismo di valutazione noto come Call-By-Name Nel modello Call-by-name:
i parametri si valutano NON all'atto della chiamata, ma solo al momento dell'effettivo uso e quindi solo se servono alla funzione si passano quindi NON valori (o indirizzi) ma bens oggetti computazionali ("eseguibili") ergo, un parametro ricevuto potrebbe non essere mai valutato se in quella invocazione non c' effettivo bisogno di lui si evita di fallire inutilmente

Conseguenza: l'insieme di funzioni che terminano con successo con questo modello pi ampio rispetto al modello applicativo.

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

35

MODELLO CALL-BY-NAME: GLI ESEMPI RIVISITATI


Se Java adottasse un modello Call-by-name, il nostro esempio terminerebbe con successo:
class Esempio { static void printOne(boolean cond, double a, double b){ if (cond) System.out.println("result = " + a); else System.out.println("result = " + b); } public static void main(String[] args) { int x=5, y=4; printOne(x>y, 3+1, 3/0); } } result = 4.0

MODELLO CALL-BY-NAME: GLI ESEMPI RIVISITATI


Se JavaScript adottasse un modello Call-by-name, il nostro esempio terminerebbe con successo:
var f = function(flag,x) { return (flag<10) ? 5 : x; } var b = f(3, abort() ); document.write("result =" + b); result = 5

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

36

MA ALLORA.. PERCH?
MA.. se il modello call-by-name cos utile, perch nei linguaggi di largo uso non stato adottato questo? Perch, nonostante i suoi vantaggi concettuali, meno efficiente nei casi normali, che sono la maggioranza
valuta i parametri pi volte per ogni chiamata (anche se si potrebbe ovviare valutandoli solo al primo uso) richiede pi risorse a run-time, dovendo gestire il passaggio di oggetti computazionali anzich semplici valori o indirizzi richiede una macchina virtuale pi sofisticata, capace di "lazy evaluation" il tutto per catturare "pochi" (?) casi .. che magari sono pure frutto di errori di programmazione..?

Inoltre, molti casi di fallimento (quelli aritmetici) si evitano arricchendo l'aritmetica con NaN e Infinity

MODELLO APPLICATIVO: L' ESEMPIO JAVA IN JAVASCRIPT


Consideriamo una versione JavaScript del precedente esempio Java :
printOne = function (cond, a, b){ if (cond) println("result = " + a); else println("result = " + b); } var x=5, y=4; printOne(x>y, 3+1, 3/0);

A differenza di quanto ci si potrebbe aspettare, questo esempio non fallisce perch la macchina virtuale JavaScript non d errore nella divisione 3/0, che un'operazione lecita con risultato Infinity Perci, il programma funziona:
result = 4

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

37

MODELLO CALL-BY-NAME: DUNQUE.. NON SI USA?


In realt, il modello call-by-name ha degli ambiti d'uso: il pi noto quello delle MACRO
Esempio di macro in C: #define f(FLAG, X) (((FLAG)<10) ? 5 : (X)) main(){ float b = f( 3, abort()); // NON D ERRORE! } Esattamente come negli esempi ipotetici rivisitati poco fa, in questo caso il C non d errore, perch abort non viene mai valutata La "chiamata" alla macro si espande infatti come: b = (((3)<10)? 5 : (abort())) che, essendo la condizione sempre vera, d come risultato 5 grazie alla valutazione in corto circuito dell'operatore ternario.

MODELLO CALL-BY-NAME: E SE VOLESSIMO USARLO..?


Sebbene non adottato nei linguaggi "mainstream", il modello call-by-name pu essere facilmente SIMULATO nei linguaggi che hanno funzioni come first-class entities In tal caso, infatti, facile ricreare a mano gli stessi passi che il modello call-by-name avrebbe fatto da solo
basta passare alla funzione non dei valori, ma oggetti computazionali che, quando eseguiti, producano i valori desiderati non vi pare che sia il ritratto delle FUNZIONI JAVASCRIPT?

Dunque, per ricreare un modello call-by-name basta:


sostituire ogni valore di parametro attuale con una funzione che ritorni tale valore quando invocata sostituire ogni uso di un parametro formale dentro la funzione con una chiamata alla funzione stessa

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

38

MODELLO CALL-BY-NAME RICOSTRUITO IN JAVASCRIPT


L'esempio JavaScript originale (modello applicativo):
var f = function(flag, x) { // sono due valori return (flag<10) ? 5 : x; // usa i due valori } var b = f(3, abort() ); document.write("result =" + b);

L'esempio ricostruito in stile call-by-name:


var f = function(flag, x) { // diventano due funzioni return (flag()<10) ? 5 : x(); // invoca le due funzioni } var b = f( function(){return 3}, function(){ abort()} ); document.write("result =" + b); result = 5

FUNZIONA !

Esperimento
Un'ottimizzazione del modello "call by name", che evita di ricalcolare n volte la stessa funzione, detta "call by need".

errore

nessun errore

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

39

MODELLO CALL-BY-NAME RICOSTRUITO IN JAVA.. ? (1)


Si riesce a ricreare un modello call-by-name in Java? s, ma.. con fatica, in modo molto meno espressivo ed elegante, perch le funzioni non sono entit di prima classe
non possibile creare una funzione e passarla: si deve passare un oggetto di un'opportuna classe.. la quale classe quindi va creata appositamente, come va creata appositamente una sua istanza singleton .. e il corpo della funzione va incapsulato in un metodo ..costringendo quindi a una pletora di classi, una per ogni codice di funzione , organizzate in gerarchia

Morale: complicato e pieno di "boilerplate code"


(Forse in futuro avremo chiusure anche in Java? Vedremo.. )

MODELLO CALL-BY-NAME RICOSTRUITO IN JAVA.. ? (2)


L'esempio Java originale (modello applicativo):
class Esempio { static void printOne(boolean cond, double a, double b){ if (cond) System.out.println("result = " + a); else System.out.println("result = " + b); riceve valori } public static void main(String[] args) { int x=5, y=4; printOne(x>y, 3+1, 3/0); } }
passa valori usa valori

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

40

MODELLO CALL-BY-NAME RICOSTRUITO IN JAVA.. ? (3)


L'esempio ricostruito in Java in stile call-by-name:
class Esempio { static void printTwo(boolean cond, MyFunction a, MyFunction b){ { if (cond) System.out.println("result = " + a.invoke() ); else System.out.println("result = " + b.invoke() ); riceve oggetti }
invoca metodi su oggetti

public static void main(String[] args) { costruisce oggetti (singleton) int x=5, y=4; printTwo(x>y, new MyFunction1(), new MyFunction2() ); } }

result = 4.0 FUNZIONA ! MyFunction un'interfaccia (o una classe astratta) che dichiara il metodo invoke classi definiscono metodi che restituiscono i valori desiderati MyFunction1 e MyFunction1 sono le due classi concrete che definiscono invoke rispettivamente come return 3+1; e return 3/0

dove:

MODELLO CALL-BY-NAME RICOSTRUITO IN C# (1)


Ricreare un modello call-by-name in C# invece molto pi semplice, perch ci sono le funzioni come entit di prima classe L'esempio ricostruito in C# in stile call-by-name:
class Esempio{ delegate double MyFunction();
tipo funzione riceve funzioni

static void printTwo(bool cond, MyFunction a, MyFunction b){ if (cond) System.Console.WriteLine("result = " + a() ); else System.Console.WriteLine("result = " + b() ); } public static void Main(string[] args) { int x=5, y=4, a=0; printTwo(x>y, () => 3+1, () => 3/a ); } }
lambda expressions: costruiscono funzioni

invoca funzioni

result = 4.0

NB: necessario usare una variabile per evitare la valutazione di corto circuito a compile time (l'efficienza di C#...)

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

41

1. LE BASI DEL LINGUAGGIO 2. IL LATO FUNZIONALE 3. IL LATO A OGGETTI 4. DOVE I DUE LATI S'INCONTRANO 5. APPLICAZIONI MULTI-PARADIGMA

Il Modello a Oggetti
Javascript adotta un modello object-based (non objectoriented), senza classi, basato sul concetto di prototipo. In questo approccio, un oggetto: NON istanza di una classe (non esistono classi!) solo una collezione di propriet (dati o funzioni), tutte pubbliche, accessibili tramite "dot notation" costruito, tramite new, da un costruttore che ne specifica la struttura (iniziale)
il nome del costruttore "quanto resta" delle classi

associato dal costruttore a un "oggetto padre", detto prototipo, di cui eredita le propriet PROTOTYPE-BASED INHERITANCE

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

42

Costruzione di oggetti
Un costruttore una normale funzione, che per: specifica le propriet e i metodi dell'oggetto da creare mediante la parola chiave this, con ci distinguendole dalle propriet e dai metodi della funzione stessa:
Point = function(i,j){ this.x = i; this.y = j; this.getX = function(){ return this.x; } this.getY = function(){ return this.y; } }

invocata indirettamente, tramite l'operatore new


p1 = new Point(3,4); p2 = new Point(0,1);

Esperimenti

Point globale, pu essere usato fuori dalla funzione test

Pippo locale, NON ESISTE fuori dalla funzione test

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

43

Costruzione inline di "object literals"


anche possibile creare oggetti "inline", elencandone direttamente le propriet mediante una sequenza di coppie nome:valore separate da virgole
p3 = { x:10, y:7 }; In questa modalit: non si possono definire metodi i nomi delle propriet possono essere stringhe qualunque (anche illecite come identificatori), purch siano messe fra virgolette p4 = { "for-me": 10, "for you": "bleah" };

Accesso e modifica delle propriet


Poich tutte le propriet sono pubbliche, sono anche liberamente modificabili: p1.x = 10; // da (3,4) diventa (10,4)
NB: in realt, esistono anche alcune propriet "di sistema", non visibili n enumerabili con gli appositi costrutti IL COSTRUTTO with Per accedere a pi propriet di uno stesso oggetto in modo rapido si pu usare il costrutto with: with (p1) x=22, y=2 with (p1) {x=3; y=4} equivale a p1.x=22, etc equivale a p1.x=3, etc

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

44

Aggiunta e rimozione di propriet


Le propriet specificate nel costruttore non sono le uniche che un oggetto pu avere: sono quelle iniziali possibile aggiungere dinamicamente nuove propriet semplicemente nominandole e usandole: p1.z = -3; // da {x:10, y:4} diventa {x:10, y:4, z: -3}
Ci possibile proprio perch non esiste il concetto di classe come "specifica di struttura" (fissa) di una collezione di oggetti

anche possibile rimuovere dinamicamente propriet, mediante l'operatore delete : delete p1.x // da {x:10, y:4, z: -3} diventa {y:4, z: -3}

La propriet constructor
Ogni oggetto tiene traccia del costruttore che lo ha creato mediante la propriet constructor
RICORDA: questo non autorizza a dedurre che l'oggetto abbia ancora tutte e sole le propriet definite dal costruttore perch nel frattempo la situazione pu essere cambiata! Ora l'oggetto potrebbe averne di pi (se ne nono state aggiunte) o di meno (se ne sono state tolte)
p1 ha le due propriet definite dal costruttore ora ne ha 3, ma il costruttore sempre lo stesso

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

45

Invocazione di metodi
Un metodo invocato dall' operatore di chiamata () document.write( p1.getX() + "<br/>") NB: un metodo inesistente provoca errore a run-time (metodo non supportato) ma non sempre viene mostrato un messaggio d'errore Attenzione, per: in caso di errore, Javascript non esegue le istruzioni successive.

Propriet e metodi "di classe"


JavaScript non ha classi, ma ha costruttori che tipicamente riflettono le "categorie" che in altri linguaggi sarebbero state classi. Pertanto, possibile modellare propriet "di classe" in termini di propriet della funzione-costruttore: p1 = new Point(3,4); Point.color = "black"; Point.commonMethod = function(...){...}
Infatti, poich una funzione un oggetto, pu avere le sue propriet ed naturale pensare che le propriet comuni a tutti i Point siano ospitate nella funzione-costruttore omonima

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

46

Propriet e metodi "privati"


Come gi detto, le propriet degli oggetti JavaScript sono tutte pubbliche, con accesso libero a chiunque Tuttavia, data la natura funzionale del linguaggio, possibile ottenere ambienti privati mediante chiusure Ci permette di modellare propriet "private" mediante funzioni accessor (pubbliche) definite nel costruttore.
Point = function(i,j){ this.getX = function(){ return i; } this.getY = function(){ return j; } } Uso: p1 = new Point(3,4); x = p1.getX(); // restituisce 3 u = p1.x; // UNDEFINED

Prototipi di oggetti
Ogni oggetto associato a un "oggetto padre", detto prototipo, di cui eredita le propriet Ci d luogo a una forma particolare di ereditariet, senza classi, detta prototype-based inheritance
con le relative "gerarchie di ereditariet prototipale"

Ogni oggetto referenzia il suo prototipo tramite una propriet (nascosta) chiamata __proto__
Oggetto oggetto padre

__proto__ propriet dell'oggetto

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

47

Prototipi di oggetti (2)


Chi sia esattamente l' "oggetto padre" di un oggetto dipende da CHI costruisce tale oggetto Esiste per un antenato comune a tutti, le cui propriet sono ereditate da tutti: lo chiameremo "The One"
non il suo vero nome, ma per ora chiamiamolo cos

Esso antenato DIRETTO di ogni "object literal" e antenato INDIRETTO di ogni altro oggetto
gli oggetti costruiti da un dato costruttore hanno infatti come padre una sorta di "prototipo di classe" che a sua volta ha come padre "The One".

Prototipi di oggetti (3)


ESEMPIO
p0 = { x:6, y:7 }; p1 = new Point(3,4); p2 = new Point(0,1);
Oggetto p0 Object literal: ha come padre The One. Oggetti costruiti dal costruttore Point: hanno come padre "The Point", sorta di prototipo di classe di tutti i Point, il cui padre a sua volta The One.

__proto__ x: 6, y: 7
Oggetto p1

The One

__proto__ x: 3, y: 4
Oggetto p2

The Point

__proto__ x: 0, y: 1

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

48

Prototipi di oggetti (4)


I prototipi sono il mezzo offerto da JavaScript per esprimere relazioni e "parentele" fra oggetti Esiste un prototipo predefinito per ogni "categoria" di oggetti: funzioni, array, numeri Point, Persona,
chiameremo "The Function" il padre di tutte le funzioni, "The Number" il padre di tutti i numeri, etc. etc. di nuovo, non il loro vero nome, ma per ora va bene..

Ma questi prototipi sono anch'essi degli oggetti, quindi hanno a loro volta un prototipo: il caro The One! Da qui nasce la tassonomia di prototipi che esprime l'ereditariet nella forma prototype-based.

Prototipi di oggetti (5)


ESEMPIO
p0 = { x:6, y:7 }; p1 = new Point(3,4); p2 = new Point(0,1);
Oggetto p0 "The Function" una sorta di prototipo di classe di tutte le funzioni, il cui padre a sua volta The One.

__proto__ x: 6, y: 7
Oggetto p1

The One

The Function
Funzione Point

__proto__ x: 3, y: 4
Oggetto p2

The Point
Funzione Point: ha come padre "The Function".

__proto__ codice

__proto__ x: 0, y: 1

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

49

Tassonomia di Prototipi
Prototipo-base di qualunque oggetto

The One

catena dei prototipi (__proto__)

The Function

The Array

The Number

The Point

Prototipo di tutti gli oggetti-funzione ( una funzione)

Prototipo di tutti gli oggetti-array ( un array)

Prototipo di tutti gli oggetti-numeri ( un numero)

Prototipo di tutti gli oggetti-Point ( un oggetto)

Esperimenti (1)
Google Chrome contiene un motore JavaScript che mostra anche la propriet __proto__, che altri motori tipicamente nascondono: ci lo rende perfetto per sperimentare. Usiamo l'ambiente JScript IDE disponibile sul sito del corso Sfrutteremo l'operatore typeof per scoprire il tipo di un oggetto, e l'operatore isPrototypeOf per risalire la catena dei prototipi.
isPrototypeOf(obj) controlla se l'oggetto corrente nella catena dei prototipo di obj

Oggetto obj

Prototipo di obj

__proto__

typeof(obj) dice il tipo dell'oggetto obj

typeof(obj.__proto__) dice il tipo del prototipo di obj

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

50

Esperimenti (2)
funz = function(x,y){ return x+y; } Point = function(i,j){ this.x=i; this.y=j; this.getX= } typeof(funz) protfunz = funz.__proto__ typeof(protfunz) typeof(protfunz.__proto__) protfunz.isPrototypeOf(funz) function function object true

Funzione funz

protfunz.isPrototypeOf(funz) vera

__proto__ codice
typeof(funz) function

The Function
protfunz lui typeof(protfunz) sempre function

The One
typeof(protfunz. __proto__) object

Esperimenti (3)
funz = function(x,y){ return x+y; } Point = function(i,j){ this.x=i; this.y=j; this.getX= } typeof(funz) protfunz = funz.__proto__ typeof(protfunz) typeof(protfunz.__proto__) protfunz.isPrototypeOf(funz) function function object true

Funzione funz

__proto__ codice

The Function

The One

In questo sistema, il padre di tutte le funzioni, da noi chiamato affettuosamente "The Function", la curiosa funzione function Empty(){}

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

51

Esperimenti (4)
Point = function(i,j){ this.x=i; this.y=j; this.getX= } p1 = new Point(3,4) pr = p1.__proto__ typeof(p1) typeof(pr) pr.isPrototypeOf(p1) p1.constructor Funzione Point __proto__

object object true Point

codice
Oggetto p1 __proto__ constructor

The Function
Oggetto pr

The One

The Point

x: 3, y: 4

Esperimenti (5)
Point = function(i,j){ this.x=i; this.y=j; this.getX= } p0 = { x:6, y:7 }; pr0 = p0.__proto__; p1 = new Point(3,4); pr1 = p1.__proto__; p2 = new Point(0,1); pr2 = p1.__proto__; pr1 == pr2 true pr0 == pr2 false pr1.__proto__ === pr0 true
p0

__proto__ x: 6, y: 7

pr0

The One
pr1

p1

__proto__ x: 3, y: 4

The Point
pr2

p2

__proto__ x: 0, y: 1

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

52

Esperimenti (6)
Point = function(i,j){ this.x=i; this.y=j; this.getX= } Pair = function(a,b){ this.a=a; this.b=b; } p0 = { x:6, y:7 }; pr0 = p0.__proto__; Pair.__proto__ === Point.__proto__ true Point.__proto__.__proto__ === pr0 true Pair.__proto__.__proto__ === pr0 true
p0

__proto__ x: 6, y: 7

pr0

The One
pr1

The Function

p1

__proto__ x: 3, y: 4

The Point
pr2 Pair Point

p2

__proto__ x: 0, y: 1

__proto__ codice

__proto__ codice

Prototipi di costruzione
Sappiamo che il prototipo di un oggetto dipende da chi lo costruisce: in particolare, gli oggetti costruiti da uno stesso costruttore condividono tutti lo stesso prototipo
una sorta di "prototipo di classe"

Il prototipo che un costruttore attribuisce agli oggetti da lui creati espresso dalla propriet prototype
prototype una propriet (pubblica) dei soli costruttori

non va confusa con la propriet __proto__ , che invece caratteristica di qualunque oggetto (e non pubblica)

Per ricordarne lo scopo, la propriet prototype spesso chiamata prototipo di costruzione

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

53

Prototipi di costruzione (2)


tramite la propriet prototype che si riescono a referenziare (indirettamente) gli oggetti predefiniti fin qui chiamati "The Function", "The Point", "The One" Infatti, se p un oggetto creato dal costruttore Point, allora per la definizione stessa di prototype si ha:
p.__proto__ == Point.prototype

In particolare, detto Object il costruttore degli oggetti, Object.prototype referenzia "The One", l'antenato comune (innominato) di tutta la tassonomia JavaScript!
MA ATTENZIONE: Object in s un costruttore, ossia una funzione.. il cui __proto__ quindi "The Function", non Object! In questo mix facile perdersi

Prototipi e costruttori predefiniti


In generale, ogni tipo di dato ha un costruttore predefinito, il cui prototype definisce il prototipo di default per quella categoria di oggetti. Dunque, in particolare: le funzioni sono costruite dal costruttore Function, che usa come prototipo quello che abbiamo chiamato "The Function"; ergo, "The Function" Function.prototype gli array sono costruiti dal costruttore Array, che usa come prototipo quello che abbiamo chiamato "The Array": perci, "The Array" Array.prototype gli object literals sono costruiti dal costruttore Object, che usa come prototipo l'oggetto base da noi chiamato "The One"; ergo, "The One" Object.prototype .. e cos via!

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

54

Esperimenti (7)
Point = function(i,j){ this.x=i; this.y=j; this.getX= } p0 = { x:6, y:7 }; pr0 = p0.__proto__; Object.prototype === pr0 true pr0.__proto__ == null true Function.prototype === Point.__proto__ true Object.__proto__ === Function.prototype true Point.prototype == pr1 true
p0

__proto__ x: 6, y: 7
pr0

The One
Object.prototype

The Function
Function.prototype

p1

__proto__ x: 3, y: 4
pr1 pr2

The Point
Pair Point

p2

__proto__ x: 0, y: 1

__proto__ codice

__proto__ codice

Esperimenti (8)
Osservare come NON sia Object la radice della gerarchia, ma bens "The One", alias Object.prototype Object "solo" un costruttore con un nome evocativo.. ossia, una normale funzione! "The One" p0 Object

__proto__ prototype codice


"The Function"
Function.prototype

__proto__ x: 6, y: 7
pr0

Object.prototype

__proto__ : null
propriet di tutti gli oggetti

__proto__
propriet di tutte le funzioni

p1

__proto__ x: 3, y: 4
pr1 pr2

"The Point"
Point.prototype

__proto__
propriet di tutti i Point

Point

__proto__ prototype codice

p2

__proto__ x: 0, y: 1

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

55

Esperimenti (9)
Con l'interprete JavaScript Rhino, dotato di debugger, possiamo vedere che Object.prototype definisce le propriet comuni a tutti gli oggetti:

Esperimenti (10)
Analogamente Function.prototype definisce le propriet comuni a tutte le funzioni:

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

56

Prototipi ed effetti a run-time


Le relazioni fra oggetti espresse dalle catene di prototipi sono effettivamente esistenti a run-time Come tali, sono dinamiche: eventuali modifiche sono immediatamente reificate nel sistema In particolare possibile, con diverse conseguenze: aggiungere/togliere propriet a un prototipo in uso sostituire un oggetto-prototipo con un altro (diverso)
in particolare, si pu sostituire il prototipo di default usato da un costruttore.. .. creando cos una NOSTRA ereditariet prototipale!

Aggiunta di propriet al prototipo: Type Augmenting


possibile aggiungere propriet al prototipo esistente, con impatto diretto e immediato sugli oggetti gi esistenti che vengono quindi modificati "al volo": la struttura del sistema cambia a run time. Questa tecnica nota come "Type Augmenting" Per farlo: non opportuno accedere al prototipo tramite la propriet __proto__ di uno specifico oggetto
sia perch in generale essa non pubblica sia perch ci richiede l'esistenza di tale oggetto

il modo corretto accedere al prototipo tramite la propriet prototype del corrispondente costruttore.

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

57

Esempio
Consideriamo il solito esempio con i soliti tre punti (un "object literal" e due costruiti dal costruttore Point):
Point = function(i,j){ this.x=i; this.y=j; this.getX= } p0 = { x:6, y:7 }; showProps(p0); p1 = new Point(3,4); showProps(p1); p2 = new Point(0,1); showProps(p2);

possibile aggiungere propriet a Point.prototype, ottenendo con ci l'effetto di farle ereditare immediatamente e dinamicamente ai due Point p1 e p2 gi esistenti
la modifica non impatta invece su p0, che fa riferimento a un prototipo diverso
Point.prototype.color = "black"
showProps(p1); showProps(p0); // ha anche la nuova propriet color! // rimasto com'era

Esperimenti (11)
Point = function(i,j){ this.x=i; this.y=j; this.getX= } p0 = { x:6, y:7 }; p1 = new Point(3,4); p2 = new Point(0,1);

Point.prototype.color = "black"
nessun impatto qui

"The One"
Object.prototype

"The Function"
Function.prototype

p0

__proto__ x: 6, y: 7

__proto__ : null
propriet di tutti gli oggetti

__proto__
propriet di tutte le funzioni

p1

__proto__ x: 3, y: 4

"The Point"
Point.prototype

percepito qui

__proto__
propriet di tutti i Point color:"black"

Point

__proto__ prototype codice

p2

__proto__ x: 0, y: 1

percepito qui

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

58

Sostituzione del prototipo: "creating & inheriting"


In alternativa, possibile sostituire il prototipo di costruzione usato da un costruttore con un altro, a nostra scelta. a differenza di poco fa, ci NON ha impatto sugli oggetti gi esistenti, dunque la struttura della parte di sistema gi in essere NON cambia a run time l'effetto cambiare le regole di costruzione da qui in poi, incapsulando strutturalmente nei costruttori le relazioni che vorremo vedere esistenti fra gli oggetti FUTURI il modo per definire una nostra ereditariet prototipale Per farlo, si reimposta la propriet prototype del costruttore, agganciandola a un opportuno oggetto-prototipo preventivamente predisposto.

Esempio
Riprendendo il solito esempio:
Point = function(i,j){ this.x=i; this.y=j; this.getX= } p1 = new Point(3,4); showProps(p1); p2 = new Point(0,1); showProps(p2);

Se ora, dopo aver gi creato p1 e p2, sostituiamo il prototipo di costruzione di Point reimpostando la propriet Point.prototype, tutti i futuri Point risulteranno diversi
i "Point futuri" non risulteranno "imparentati" coi "Point vecchi", facendo parte di diverse catene prototipali
Point.prototype = { color: "black", setX: function(x){ this.x = x; } }; } showProps(p1); // rimasto tutto com'era p3 = new Point(2,6); showProps(p3); // ha anche le nuove propriet! p3.setX(9); // funziona e cambia l'ascissa a 9 !!

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

59

Esperimenti (12a)
Point = function(i,j){ this.x=i; this.y=j; this.getX= } p1 = new Point(3,4);

"The One"
Object.prototype

"The Function"
Function.prototype

__proto__ : null
propriet di tutti gli oggetti

__proto__
propriet di tutte le funzioni

"The Point"
Point.prototype

p1

__proto__ x: 3, y: 4

__proto__
propriet di tutti i Point

Point

__proto__ prototype codice

Esperimenti (12b)
Point = function(i,j){ this.x=i; this.y=j; this.getX= } p1 = new Point(3,4); Point.prototype = { color:"black", setX:function(x){ this.x = x; } }; p3 = new Point(2,6); p3.setX(9)
nuovo prototipo "The One"
Object.prototype

"The Function"
Function.prototype

__proto__ color : black setX: .

__proto__ : null
propriet di tutti gli oggetti

__proto__
propriet di tutte le funzioni

"The Point"
Point.prototype

p1

__proto__ x: 3, y: 4

__proto__
propriet di tutti i Point

Point

__proto__ prototype codice


SOSTITUITO !

p3

__proto__ x: 2, y: 6

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

60

Ereditariet Prototype-based: definire le proprie tassonomie


L'approccio precedente usato tipicamente per definire le relazioni di ereditariet fra i propri tipi. Per default, infatti, i costruttori usano come prototipo di costruzione un oggetto diverso per ogni costruttore
"The Point" per i Point, "The Pair" per i Pair, etc

ma ci porta ad avere oggetti senza parentele fra loro


a parte l'antenato comune "The One"

Per avere oggetti in relazione padre/figlio ("is a") occorre impostare i prototipi di costruzione dei costruttori in modo da riflettere la tassonomia desiderata.
l'ereditariet fra classi dei linguaggi class-based diviene qui un'ereditariet prototipale espressa da prototype

Esempio (1)
Supponiamo di voler esprimere l'idea che la categoria Studente "erediti" dalla categoria Persona Occorre definire il costruttore Persona in accordo alle propriet desiderate per le persone. Occorre poi definire il costruttore Studente in accordo alle propriet desiderate per gli studenti.
ma se ci fermiamo qui, Studente una categoria "stand-alone", non imparentata con Persona

Per esprimere il fatto che Studente "is-a" Persona, occorre far s che gli oggetti-studente che saranno costruiti abbiano come prototipo una persona
.. e non "The Student" o un altro oggetto che non ha nulla a che fare con le persone

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

61

Esempio (2)
Per far s che gli oggetti-studente che saranno costruiti abbiano come prototipo una persona, occorre: scegliere un oggetto-persona che faccia da prototipo per tutti gli studenti (che sia bello) impostare su esso Studente.prototype, in modo che tutti gli Studenti che verranno creati facciano riferimento a tale Persona. per mantenere la coerenza globale, reimpostare parallelamente la propriet constructor della persona prototipo (che ovviamente punterebbe al costruttore Persona) in modo che punti al costruttore Studente
in tal modo, chiedendo a un oggetto Studente chi sia il suo constructor, risponder correttamente Studente

Esempio (3)
Per prima cosa, definiamo il costruttore Persona:
Persona = function(nome, annoNascita){ this.nome = nome; this.anno = annoNascita; this.toString = function(){ return this.nome + " nata nel " + this.anno } }

Poi definiamo il costruttore (stand-alone) Studente:


Studente = function(nome, annoNascita, matricola){ this.nome = nome; this.anno = annoNascita; this.matricola = matricola; this.toString = function(){ return this.nome + " nata nel " + this.anno + " e la sua matricola " + this.matricola } }

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

62

Esempio (4)
Ora procuriamoci una specifica (e bella!) Persona, destinata a fare da prototipo a tutti gli studenti:
personaBellissima = new Persona("zio", 1900);

Finalmente impostiamo il prototipo di costruzione di Studente in modo che faccia riferimento ad essa: Studente.prototype = personaBellissima; Last but not least, per garantire la coerenza interna, impostiamo la propriet constructor del neo-prototipo in modo che punti al costruttore Studente: Studente.prototype.constructor = Studente

Esperimenti (13a)
"The One"
Object.prototype

"The Function"
Function.prototype

__proto__ : null
propriet di tutti gli oggetti

__proto__
propriet di tutte le funzioni

"The Person"
Persona.prototype

Persona

__proto__ prototype codice


Studente

__proto__
propriet di tutte le Persone

"The Student"
Studente.prototype

__proto__ prototype codice

__proto__
propriet di tutti gli Studenti

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

63

Esperimenti (13b)
"The One"
Object.prototype s = new Studente( "John", 1995,9000234532)

"The Function"
Function.prototype

__proto__ : null
propriet di tutti gli oggetti

__proto__
propriet di tutte le funzioni

__proto__
nome: "John" anno: 1995
matricola: 9000234532

"The Person"
Persona.prototype

Persona

__proto__ prototype codice


Studente

__proto__
propriet di tutte le Persone

personaBellissima

VECCHIO
Studente.prototype

__proto__ nome: zio anno: 1900


constructor

__proto__ prototype codice

__proto__
propriet di tutti gli Studenti

Esperimenti (13c)
personaBellissima.hasOwnProperty("constructor") s.hasOwnProperty("constructor") s.constructor true false Studente

__proto__
nome: "John" anno: 1995
matricola: 9000234532

"The Person"
Persona.prototype

Persona

__proto__ prototype codice


Studente

__proto__
propriet di tutte le Persone

personaBellissima

VECCHIO
Studente.prototype

__proto__ nome: zio anno: 1900


constructor

__proto__ prototype codice

__proto__
propriet di tutti gli Studenti

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

64

Ereditariet: "super" nei costruttori


Non avendo una "superclasse", JavaScript non pu offrire una parola chiave super per invocare il costruttore del "piano di sopra". Tuttavia, ci pu essere emulato tramite il metodo call che permette la chiamata indiretta di una funzione. Ad esempio, il costruttore Studente potrebbe essere cos ridefinito:
Studente = function(nome, annoNascita, matricola){ Persona.call(this, nome, annoNascita); this.matricola = matricola; this.toString = function(){ return this.nome + " nata nel " + this.anno + " e la sua matricola " + this.matricola } }

Ereditariet: "super" nei metodi


Analogamente, non possibile neppure invocare un metodo definito nel "piano di sopra" .. ma anche questo pu essere emulato tramite call sfruttando all'uopo la propriet prototype. Ad esempio, per richiamare la toString di Persona dall'interno della toString di Studente, basta scrivere:
Studente = function(nome, annoNascita, matricola){ Persona.call(this, nome, annoNascita); this.matricola = matricola; this.toString = function(){ return Studente.prototype.toString.call(this) + " e la sua matricola " + this.matricola } }

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

65

L'Oggetto Globale
Si visto che JavaScript ammette variabili e funzioni globali , ossia non definite dentro un'altra funzione o di un oggetto. Ma dov' questo "ambiente globale"? In realt, non esiste un ambiente globale nel senso tradizionale del termine: semplicemente, variabili e funzioni "globali" sono attribuiti a un oggetto globale , avente
come metodi, tutte le funzioni predefinite pi tutte quelle definite dall'utente a livello "globale" come dati, tutte le variabili globali

Curiosamente, l'oggetto globale deve esistere, ma non prestabilito n CHI sia, n CHE NOME debba avere: dipende dal contesto e dallo specifico motore JavaScript.

L'Oggetto Globale (2)


In un browser Web, l'oggetto globale solitamente coincide con l'oggetto window In un server, invece, probabile che tale ruolo sia svolto dall'oggetto response In altri motori stand-alone, come Rhino, l'oggetto globale sar qualcos'altro ancora MA.. IMPORTANTE SAPERLO? NO, se si usano propriet e funzioni globali in modo "normale", senza meta-livelli che facciano emergere l'oggetto sottostante (cio nella maggioranza dei casi) S, se si usa eval o un'altra funzione riflessiva perch in tal caso cambia l'environment di valutazione!

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

66

L'Oggetto Globale (3)


Ad esempio, eval("var f") diverso da var f la prima definizione avviene in uno scope non globale la seconda avviene invece in uno scope globale Come distinguere dove ci si trova? Non sempre semplice ..per, se si conosce qual l'oggetto globale nel contesto di riferimento, si pu fare un facile test. Ad esempio, in un browser Web, in cui l'oggetto globale solitamente coincide con l'oggetto window, si pu verificare se la variabile f globale scrivendo: f === window.f

L'Oggetto Globale (4)


L'oggetto globale contiene svariate funzioni predefinite: eval valuta il programma Javascript passato come stringa (riflessione, intercessione)
escape converte una stringa nel formato portabile: i caratteri non consentiti sono sostituiti da "sequenze di escape" (es. %20 per ' ') riporta una stringa da formato portabile a formato originale

unescape

isFinite classiche funzioni dall'ovvio significato isNaN parseFloat parseInt

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

67

L'Oggetto Globale (5)


L'oggetto globale contiene anche i costruttori predefiniti:
Object Array Boolean Function Number String Date RegExp classici costruttori gi incontrati e discussi

definisce i concetti per esprimere date e orari fornisce il supporto per le espressioni regolari. contiene la libreria matematica (non va istanziato)

nonch alcuni oggetti predefiniti:


Math

1. LE BASI DEL LINGUAGGIO 2. IL LATO FUNZIONALE 3. IL LATO A OGGETTI 4. DOVE I DUE LATI S'INCONTRANO 5. APPLICAZIONI MULTI-PARADIGMA

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

68

Il costruttore Function
In precedenza abbiamo discusso molto di funzioni. Ora sappiamo che ogni funzione JavaScript un oggetto, costruito (direttamente o meno) dal costruttore Function In particolare, tale costruzione pu avvenire: implicitamente, quando si usa il costrutto function (come abbiamo fatto sempre finora)
in tal caso, gli argomenti sono i parametri formali della funzione e il corpo (codice) della funzione racchiuso in un blocco

esplicitamente, quando si usa il costruttore Function


in tal caso, gli argomenti sono tutte stringhe i primi N-1 sono i nomi dei parametri della funzione l'ultimo il corpo (codice) della funzione

Esempi
Esempio di costruzione implicita square = function(x){ return x*x }
gli argomenti sono i parametri formali della funzione il corpo della funzione cablato nel codice e racchiuso in un blocco

Esempio di costruzione esplicita square = new Function("x", "return x*x")


qui, invece, sia gli argomenti della funzione sia il corpo della stessa sono stringhe dunque, non necessariamente sono costanti! il corpo della funzione NON pi cablato nel codice, ma diviene esso stesso un argomento!

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

69

Esempi
Esempio di costruzione implicita square = function(x){ return x*x }
gli argomenti a partireparametri formali della funzione sono i da dati embedded nel programma Costruisce efficiente, perch valutato una nel codice il corpo della funzione cablatosola volta ma non flessibile, perch e racchiuso in un blocco il corpo cablato nel codice

Esempio di costruzione esplicita square = new Function("x", "return x*x")


qui, invece, sia gli argomenti della funzione sia il corpo dellaCostruisce a partire da stringhe passate al costruttore stessa sono stringhe meno efficiente, perch ri-valutato ogni volta dunque, non necessariamente corpo non cablato ma molto flessibile, perch il sono costanti! il corpo della funzione NON pi cablato nel codice, ma diviene esso stesso un argomento!

Funzioni come dati


La costruzione dinamica tramite il costruttore Function permette di sintetizzare dinamicamente il codice della funzione desiderata, creando "al volo" un oggetto-funzione ed eseguendolo subito dopo! Ci permette scenari ad alta dinamicit, come il seguente: l'utente scrive da tastiera il testo (espressione) della funzione desiderata, sotto forma di stringa il costruttore Function crea al volo una funzione che fa esattamente quanto richiesto l'utente, un istante dopo, pu eseguire la sua funzione come se fosse sempre stata parte dell'ambiente!

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

70

Funzioni come dati: esempio (1)


Ad esempio: var funz = prompt("Scrivere f(x): ") var x = prompt("Calcolare per x = ? ") var f = new Function("x", "return " + funz) confirm("Risultato: " + f(x) ) Se l'utente scrive x*x-1 4 si esegue return x*x-1 e si ottiene 15 41

ma attenzione a cosa l'utente scrive! x+1 4 return x+1

perch gli argomenti sono stringhe e quindi l'operatore + significa concatenazione di stringhe, non somma!

Funzioni come dati: esempio (2)


La prima invocazione aveva funzionato perch usava solo operatori di moltiplicazione e sottrazione, che avevano indotto una conversione implicita di tipo. Per far funzionare sempre il tutto, serve una conversione di tipo esplicita: la scelta pi semplice agire sul tipo di x var x = parseInt("Calcolare per x = ? ")) Ora, se l'utente scrive x*x-1 4 x+2 4 si esegue return x*x-1 return x+2 e si ottiene 15 6

perch l'argomento x ora un intero (typeof(x)=number), quindi l'operatore + ha il significato di somma fra numeri.

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

71

Funzioni come dati: esempio (3)

Costruttori Function: propriet


Propriet statiche: (esistono anche mentre non esegue) length - numero di parametri formali (attesi) Propriet dinamiche: (mentre la funzione in esecuzione) arguments - array contenente i parametri attuali arguments.length - numero dei parametri attuali arguments.callee - la funzione in esecuzione stessa caller - il chiamante (null se invocata da top level) constructor - riferimento all'oggetto costruttore prototype - riferimento all'oggetto prototipo

Metodi invocabili su un oggetto funzione toString - una stringa fissa (ma si pu cambiare) valueOf - ritorna la funzione stessa call e apply supportano i pattern di chiamata indiretta

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

72

Pattern per invocazioni indirette di funzioni: call & apply


Finora abbiamo sempre invocato le funzioni nel modo classico diretto, "per nome", mediante l'operatore () Tuttavia, il modo diretto non il solo possibile: possono essere utili forme di chiamata indiretta, in cui
anzich invocare il metodo method sull'oggetto obj con argomenti args, secondo l'usuale pattern

obj.method(args)
si scambiano le parti, invocando sull'oggetto-metodo method il metodo call o apply con primo argomento l'oggetto obj e successivi argomenti args, secondo i pattern

method.call(obj, args) method.apply(obj, args)

Pattern per invocazioni indirette di funzioni: call & apply


Finora abbiamo sempre invocato le funzioni nel modo una forma ibrida: classico diretto, "per nome", mediante l'operatore () Tuttavia, il modo diretto non il solo possibile: posso"classica" no ..ma calata forme di chiamata indiretta, in cui essere utili in un contesto object-oriented
anzich invocare il c', ma non il destinatario delobj con L'oggetto target obj metodo method sull'oggetto argomenti args, secondo uno scambio di ruoli. messaggio di chiamata: c' l'usuale pattern
per un certo verso, pi da chiamata di funzione

obj.method(args)
I due pattern si differenziano si scambiano le parti, invocando sull'oggetto-metodo solo per il diverso formato method il metodo call o apply con primo argomento l'ogdegli argomenti args. getto obj e successivi argomenti args, secondo i pattern

method.call(obj, args) method.apply(obj, args)

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

73

call & apply: Esempio 1


Definizione dell'oggetto funzione: test = function(x, y, z){ return x + y + z } Invocazione nel contesto corrente di test(3,4,5): test.apply(obj, [3,4,5] ) test.call(obj, 8, 1, -2 ) I parametri sono opzionali: se non ne esistono, apply e call assumono ovviamente la medesima forma. In questo esempio, l'oggetto target obj irrilevante, in quanto la funzione test invocata indipendente dall'ambiente di esecuzione (ovvero, non fa alcun riferimento a this nel suo corpo).
array di costanti

call & apply: Esempio 2


Questa funzione invece fa riferimento a this: prova = function(v){ return v + this.x } Quindi, l'oggetto destinatario del messaggio diventa rilevante perch determina l'environment di valutazione di x 1caso
x = 88 prova.call(this,3)

2 caso
x = 88 function Obj(u){ this.x = u } obj = new Obj(-4) prova.call(obj,3)

Restituisce 3 + 88 = 91

Restituisce 3 + -4 = -1

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

74

Costruttori di Array
Un array Javascript un'entit a met strada fra un array e una lista
come in Java, gli elementi si numerano da 0, length d la lunghezza dell'array, si usa la notazione parentesi quadre a differenza di Java, qui per length d la lunghezza dinamica (attuale) dell'array e non c' il vincolo di omogeneit in tipo: le celle contengono oggetti - cio qualunque cosa

costruito sulla base del costruttore Array


si parte con un dato contenuto iniziale (anche vuoto): colori = new Array("rosso", "verde", "blu") o anche, per array di costanti, con la notazione [..]: varie = ["ciao", 13, Math.sin] si aggiungono poi nuovi elementi dinamicamente colori[3] = "giallo"

Oggetti come Array (1/2)


L'aspetto di grande interesse degli array JavaScript che costituiscono il supporto per gli oggetti
infatti, poich ogni oggetto JavaScript caratterizzato in ogni istante da un insieme variabile di propriet, un array un modo pratico ed efficace per supportarlo

Tale mapping oggetti / array si spinge al punto da permettere la notazione "array-like" per accedere per nome alle propriet degli oggetti
nel quotidiano, per accedere alla propriet x dell'oggetto obj si usa la notazione puntata nella forma obj.x tuttavia, questo approccio richiede di sapere gi che l'oggetto abbia una propriet x: il nome cablato nel codice al contrario, la notazione array-like permette di accedere a propriet di oggetti senza cablarne il nome nel codice

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

75

Oggetti come Array (2/2)


Con la notazione array-like obj[propertyname] diviene possibile accedere a una propriet il cui nome non sia noto a priori, in quanto propertyname pu anche essere una variabile (stringa) Dunque, propriet di nome noto a priori sono accessibili in due modi:
mediante la notazione puntata classica obj.x mediante la notazione array-like obj.["x"]

Viceversa, propriet di nome NON noto a priori sono accessibili solo con la notazione array-like:
scrivendo obj.[propname] dove propname una stringa che conterr a run time il nome delle propriet di interesse.

Introspezione
La possibilit di aggiungere /togliere dinamicamente propriet a un oggetto pone il problema di scoprire quali propriet esso abbia in un dato istante, ossia di procedere alla sua introspezione A questo fine offerto il costrutto:

for (variabile in oggetto)


che itera sui NOMI di tutte le propriet dell'oggetto
Non sono visibili le propriet con l'attributo DontEnum settato

Ad esempio, per elencare i nomi di tutte le propriet:


function show(ogg){ for (var pName in ogg) document.write("propriet: " + pName +"<BR>") }

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

76

Da Introspezione a Intercessione
Con il costrutto introspettivo for(..in..) possibile scoprire i nomi dele propriet (visibili) di un oggetto:
function show(ogg){ for (var pName in ogg) document.write("propriet: " + pName +"<BR>") }

Per accedere a tali propriet a partire dai nomi della propriet stesse serve appunto la notazione array-like! Ad esempio, per elencarne i tipi, si pu scrivere:
function show(ogg){ for (var pName in ogg) { document.write("propriet: " + pName + ", tipo " + typeof(ogg[pName]) + "<BR>") }

Notazione array-like: Esempio


Ad esempio, l'invocazione show(p1); show(p2) sui due oggetti Point definiti tempo addietro produce propriet: propriet: propriet: propriet: propriet: propriet: propriet: propriet: propriet: x, tipo number y, tipo number z, tipo number getX, tipo function getY, tipo function x, tipo number y, tipo number getX, tipo function getY, tipo function

p1

p2

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

77

Notazione array-like e funzioni come dati


La notazione array-like offre un mezzo per sintetizzare funzioni alternativo al costruttore Function il costruttore Function permette di sintetizzare funzioni con un corpo qualunque, espresso come stringa la notazione array-like non arriva a tanto, ma permette di selezionare per nome funzioni da un elenco var fname = prompt("Nome funzione?") var f = listOfFunctions[fname] L'approccio molto pi pratico ed efficiente che dover selezionare la funzione richiesta tramite catena di if (o switch, se supportano le label stringa), soprattutto perch non vincola a un insieme pre-cablato di funzioni.

1. LE BASI DEL LINGUAGGIO 2. IL LATO FUNZIONALE 3. IL LATO A OGGETTI 4. DOVE I DUE LATI S'INCONTRANO 5. APPLICAZIONI MULTI-PARADIGMA

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

78

Rhino: JavaScript for Java


www.mozilla.org/rhino/

Rhino: JavaScript for Java


Nato nel 1997 come costola di un browser Java-based di Netscape Originariamente compilava il codice JavaScript in bytecode: oggi lavora di default in modo interpretato

Usabile sia come interprete stand-alone, sia come componente interoperabile con Java Programmazione multi-linguaggio e multi-paradigma
Accesso da JavaScript a package e classi Java

Scripting Java

JavaScript

Java
Embedding Rhino

Inserire linterprete JavaScript allinterno di applicazioni Java

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

79

Rhino: l'interprete dei comandi


java -jar js.jar [source.js] Avvia la shell di Rhino (ovvero, l'interprete JavaScript). Linterprete esegue istruzioni JavaScript memorizzando oggetti, funzioni e variabili in un contesto che rimane disponibile fino alla chiusura dellinterprete. (CTRL+C).

Scripting Java da Rhino: accedere ai package Java


La shell di Rhino definisce:
una variabile globale Packages per accedere ai package java e com. una variabile globale java = Packages.java una funzione importPackages analoga alla direttiva import di Java inoltre possibile usare classi Java scritte da noi.

Riferimenti:
http://www.mozilla.org/rhino/scriptjava.html https://developer.mozilla.org/en/Scripting_Java

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

80

Scripting Java da Rhino:


scrittura & rilettura da file di testo
js> fileName = data.txt; js> fw = new java.io.FileWriter(fileName); js> fw.write(Hello World!); js> fw.close(); In alternativa, senza importPackage: js> f = new java.io.BufferedReader() js> importPackage(java.io); js> f = new BufferedReader(new FileReader(fileName)); js> while((line = f.readLine()) != null) line; Hello World! js> Si pu ovviamente anche leggere un
file di testo gi esistente

Scripting Java da Rhino:


implementare in Rhino interfacce Java
Oggetti JavaScript possono implementare interfacce Java Qui l'oggetto obj implementa Runnable, definendo cos in Javascript il comportamento di un thread Java .. che subito dopo pu quindi essere eseguito!
Esempio: Loggetto Javascript obj js> obj = { run: function(){ print(running...) }}; definisce il metodo run. js> r = new java.lang.Runnable(obj); js> t = new java.lang.Thread(r); Thread[Thread-0,5,main] Istruzione errata in Java (Runnable js> t.start(); un' interfaccia, non ha costruttori!) js> running...
ma corretta in Rhino dove viene interpretata come creazione di un wrapper delloggetto Javascript obj

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

81

Scripting Java da Rhino:


il costruttore JavaAdapter
Lapproccio precedente:
non consente di implementare interfacce multiple; permette di estendere solo interfacce o classi astratte.
JavaAdapter( javaInterfaceOrClass, [javaInterfaceOrClass, ...], javascriptObject)
Interfaccia o classe Java

Oggetto JavaScript che estende le classi e implementa le interfacce. Lereditariet usa gli stessi meccanismi di Java (ereditariet multipla delle sole interfacce)

Scripting Java da Rhino:


un esempio completo
Tutti questi metodi saranno ereditati a runtime da java.awt.Frame.

LactionListener per il Button pu essere tranquillamente agganciato e modificato a runtime.

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

82

Embedding Rhino in Java


Per eseguire uno script Rhino in Java, occorrono:
un contesto, per mantenere le informazioni thread-specific oggetto Context uno scope, per mantenere le informazioni script-specific (variabili globali, oggetti standard, etc) oggetto Scope lo script, sotto forma di stringa o di Reader da cui leggerlo un dominio di sicurezza, in cui eseguirlo (anche null)

L'esecuzione effettiva poi lanciata dai metodi:


Object evaluateString(scope, source, , securityDomain) Object evaluateReader(scope, source, , securityDomain)

Embedding Rhino in Java:


Context & Scope
Un Context mantiene le informazioni thread-specific sullambiente di esecuzione di uno script
Context ctx = Context.enter() associa il thread corrente ad un Context e lo restituisce Context.exit() rilascia il contesto

Uno Scope un set di oggetti JavaScript che mantiene le informazioni script-specific sull'ambiente di esecuzione:
Variabili globali dello script; Oggetti standard (Function, Object...)

Viene creato da un Context, ma indipendente da esso:


ScriptableObject scope = ctx.initStandardObjects();

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

83

Embedding Rhino in Java:


Esempio di esecuzione
Object evaluateString( Scriptable scope, String source, String sourceName, int lineNumber, Object securityDomain )
// creazione del contesto Context ctx = Context.enter(); // creazione e inizializzazione dello scope SecurityDomain nullo ScriptableObject scope = ctx.initStandardObjects(); nessuna politica di // definizione del testo del programma JavaScript sicurezza String script = java.lang.System.out.println(hello world!); // esecuzione dello script Object result = ctx.evaluateString(scope, script, Mia Prova, 1, null); // Stampa del risultato System.out.println( ctx.toString(result) ); Numero di riga // Esco dal contesto Strnga stampata dell'eventuale errore Context.exit(); su stderr in caso stampato su stderr di errore (utile a (utile per debugging) fini di debugging)

Non finisce qui


Grazie a Rhino si possono:
Usare oggetti JavaScript da Java; Wrappare oggetti Java in oggetti JavaScript; Tradurre sorgenti JavaScript in bytecode Java (JavaScript Compiler: http://www.mozilla.org/rhino/jsc.html); Effettuare il debug del codice JavaScript: (Rhino Debugger: http://www.mozilla.org/rhino/debugger.html).

Prof. Enrico Denti - Universit di Bologna A.A. 2010/2011

84

You might also like