Curs2 C#

10
FUNCTORI (Closures) în Groovy Notiunea de "closure" (închidere) este preluatã din limbajele functionale, ca si cea de "functor" (obiect functie); limbajul C# foloseste cuvântul "delegate" pentru aceeasi constructie (o functie tratatã ca obiect). In Groovy un functor este o secventã de cod între acolade (cu parametri) care poate fi atribuitã unei variabile (referintã), poate fi transmisã ca argument unei functii (sau unui alt functor) si care poate constitui rezultatul unei functii (sau unui functor). Un functor poate fi folosit la fel ca orice alt obiect si are tipul implicit "Closure" (tip care poate fi folosit si în declararea unor variabile/argumente). In Java o clasã cu o singurã functie poate fi definitã ca o clasã top- level sau ca o clasã inclusã cu sau fãrã nume; clasa trebuie instantiatã pentru a folosi un obiect ce contine o functie. Forma Java cea mai apropiatã de un functor Groovy este o clasã inclusã anonimã definitã în momentul instantierii ei (instantiere care poate avea loc chiar în instructiunea care foloseste obiectul). Un functor Groovy nu necesitã definirea unei clase care sã respecte o anumitã interfatã (un functor nu este legat de o anumitã interfatã). In Groovy existã mai multe functii predefinite care au ca argument un functor; acest functor se poate defini în prealabil (si capãtã un nume) sau chiar în momentul folosirii lui (anonim). Utilizarea de functori are ca efect un cod sursã mai compact si mai usor de înteles (permit o exprimare mai directã si mai naturalã a unor actiuni). Sintaxa pentru definirea si utilizarea de functori Instructiunile dintr-un functor sunt încadrate de acolade, dar compilatorul Groovy deosebeste un bloc de cod de un functor. Un bloc poate sã aparã în definitia unei clase, a unei interfete sau a unei metode, în instructiunile if,while,for,do,switch,try sau în initializari (de vectori, de ex.). Orice altã utilizare a unui bloc între acolade este consideratã ca un functor. Primul exemplu este un functor fãrã parametri care afiseazã o linie albã: def nl ={ println ''} // definire functor cu nume 3.times (nl) // utilizare functor nl 1

description

C#

Transcript of Curs2 C#

Page 1: Curs2 C#

FUNCTORI (Closures) în Groovy

Notiunea de "closure" (închidere) este preluatã din limbajele functionale, ca si cea de "functor" (obiect functie); limbajul C# foloseste cuvântul "delegate" pentru aceeasi constructie (o functie tratatã ca obiect).

In Groovy un functor este o secventã de cod între acolade (cu parametri) care poate fi atribuitã unei variabile (referintã), poate fi transmisã ca argument unei functii (sau unui alt functor) si care poate constitui rezultatul unei functii (sau unui functor).

Un functor poate fi folosit la fel ca orice alt obiect si are tipul implicit "Closure" (tip care poate fi folosit si în declararea unor variabile/argumente).

In Java o clasã cu o singurã functie poate fi definitã ca o clasã top-level sau ca o clasã inclusã cu sau fãrã nume; clasa trebuie instantiatã pentru a folosi un obiect ce contine o functie. Forma Java cea mai apropiatã de un functor Groovy este o clasã inclusã anonimã definitã în momentul instantierii ei (instantiere care poate avea loc chiar în instructiunea care foloseste obiectul).

Un functor Groovy nu necesitã definirea unei clase care sã respecte o anumitã interfatã (un functor nu este legat de o anumitã interfatã).

In Groovy existã mai multe functii predefinite care au ca argument un functor; acest functor se poate defini în prealabil (si capãtã un nume) sau chiar în momentul folosirii lui (anonim).

Utilizarea de functori are ca efect un cod sursã mai compact si mai usor de înteles (permit o exprimare mai directã si mai naturalã a unor actiuni).

Sintaxa pentru definirea si utilizarea de functori

Instructiunile dintr-un functor sunt încadrate de acolade, dar compilatorul Groovy deosebeste un bloc de cod de un functor. Un bloc poate sã aparã în definitia unei clase, a unei interfete sau a unei metode, în instructiunile if,while,for,do,switch,try sau în initializari (de vectori, de ex.).

Orice altã utilizare a unui bloc între acolade este consideratã ca un functor.Primul exemplu este un functor fãrã parametri care afiseazã o linie albã:

def nl ={ println ''} // definire functor cu nume 3.times (nl) // utilizare functor nl 3.times nl // alta utilizare functor nl

De multe ori functorul este definit chiar în locul unde este folosit si nu are nume. Ex: 3.times { println ()} // scrie 3 linii albe

De observat absenta parantezelor pentru argumentul functiei "times"; forma anterioarã este o forma abreviatã pentru: 3.times ( { println()} )

Functia "println" din functor trebuie sã aibã fie paranteze, fie un argument deoarece, în lipsa acestora, este consideratã ca o variabilã din scriptul (din spatiul de nume) în care este definit functorul, dar nu existã o variabilã cu acest nume. Un functor poate fi apelat ca o functie folosind metoda "call" (din clasa “Closure”). Ex:

def wline = { def ln=''; 50.times{ln+='-'}; println (ln)} wline.call()

Un functor poate avea un rezultat. In exemplul urmãtor functorul are ca rezultat un sir:

1

Page 2: Curs2 C#

def line = { def ln=''; 50.times{ln+='-'};return ln} print line.call()

Instructiunea "return" poate lipsi dacã rezultatul functorului este acelasi cu rezultatul ultimei instructiuni:

def line = {def ln=''; 50.times {ln +='-'}; ln+='\n'}

In general, instructiunile dintr-un functor pot folosi variabile din contextul în care el este apelat. Utilitatea acestei reguli este vizibilã în exemplul urmãtor de script care calculeazã iterativ primele 10 numere din sirul Fibonacci dupã relatia de recurentã f2=f0+f1 (initial f0=f1=1):

f0=1f1=110.times { print f0+' ' f2=f0+f1 f0=f1 f1=f2 }

De remarcat cum secventa dintre acolade foloseste variabilele f0,f1 declarate în exteriorul blocului de cod (dar în acelasi script unde este definit functorul). In acest exemplu functorul este definit acolo unde este folosit.

Dacã avem deja definitã o functie si vrem sã o folosim ca functor atunci se foloseste notatia Closure cl = this.&fun // intr-un script Groovy Closure cl = obj.&fun // obj este o instanta a unei claseExemplu:

def cmmdcr(a,b) { // alg. Euclid recursiv if (b==0) return a return cmmdcr (b,a%b)}def rec=this.&cmmdcr // sau: Closure rec=this.&cmmdrprintln rec(12,39)

Obiectele functor apartin clasei "groovy.lang.Closure", iar cuvântul "Closure" poate fi folosit în declararea de variabile (inclusiv proprietãti ale unei clase), de argumente si de functii, ca orice alt tip clasã. Exemplu:

// calcul timp de rulare 'action' de n ori def calcTime(n,Closure action) { start=System.currentTimeMillis() n.times {action (222,5)} stop=System.currentTimeMillis() return stop-start}println calcTime (1000,rec) // repeta de 1000 de ori calculul cmmdc(222,5)

Dupã contextul în care este definit un functor putem avea urmãtoarele situatii:- Functor definit într-un script;- Functor definit într-un obiect (într-o clasã)

2

Page 3: Curs2 C#

- Functor definit în alt functorCuvintele cheie “owner” si “delegate” se referã la obiectul în care e continut un functor

(“enclosing object”) si de cele mai multe ori au aceeasi valoare ca si “this”(o referintã); metoda Closure.setDelegate poate modifica valoarea variabilei “delegate”. Exemplu de functor definit într-o metodã dintr-o clasã:

class Cls { void methodClosure() { def methodClosure = { println "Method Closure this is : " + this.class assert owner == this assert delegate == this } methodClosure() }}new Cls().methodClosure()

Exemplu de functor definit într-un functor:

def outer = { arg -> println "Outer: ${this.class} ${delegate.class}" assert this == owner assert delegate == this def inner = { println "Inner: ${this.class} ${delegate.class}" assert owner == arg assert delegate == arg } assert inner.delegate == arg inner()}outer (outer)

Cuvântul “delegate” este folosit în metaprogramare, atunci când se adaugã noi proprietãti sau metode unui obiect prin definire de functori în metaclasã. Exemplu în paragraful urmãtor.

Functori cu argumente

Un functor poate avea argumente; dacã este un singur argument, acesta poate folosi numele implicit "it" si nu trebuie declarat explicit (dar poate fi). Exemplele urmãtoare folosesc argumentul implicit "it":

// functor anonim definit ad-hoc 1.upto(10) { println "Radical din $it = ${Math.sqrt(it)} " }

// functor cu nume def sqrt= { println "Radical din $it = ${Math.sqrt(it)} " } for (k in 1..10) sqrt.call(k) // forma extinsa de folosire for (k in 1..10) sqrt(k) // forma scurta

Parametrii efectivi pot fi transmisi prin metoda "call" (din clasa "Closure") sau direct dupã numele functorului (utilizat ca o functie).

3

Page 4: Curs2 C#

In cazul parcurgerii unui dictionar cuvântul cheie "it" se referã la o pereche cheie-valoare. Exemplu de creare a unui dictionar invers (în care cheia din dictionarul initial devine valoare iar valoarea devine cheie în noul dictionar):

romeng =['unu':'one','doi':'two','trei':'three'] // roman-englezromeng.each { println it.key+'='+it.value} // afisareengrom=[:] // englez-romanromeng.each {engrom[it.value]=it.key }println engrom // afisare cu Map.toString

Exemplu de functor cu douã argumente folosit drept comparator la sortarea unei liste:

fruit = [ "apple", "Orange", "Avocado", "pear", "cherry" ]fruit.sort { a,b -> a.size()- b.size() } // odonare dupa lungime siruri

Este posibil sã definim si tipul argumentelor, pentru a face codul mai usor de înteles. Exemplu:

def word = { String s, int k -> s.split(' ')[k] } // functor cu doua argumente

Exemplu de ordonare a unei liste de obiecte dupã orice proprietate:

class Book { String title String author int year String toString() { "$title $author ($year) "}} // ordonare lista dupa o proprietate datadef sort( list, prop) { return list.sort { p1, p2 -> p1."${prop}" <=> p2."${prop}" } }def a=[new Book(title:'Java',author:'Gosling',year:2000), new Book(title:'C',author:'Ritche',year:1980), new Book(title:'Groovy',author:'Laforge',year:2006)]sort (a,'year')a.each {println it }

Exemplu de adãugare a unei functii (metode) care sã extragã un cuvânt dintr-un sir:

def word = { s, k -> s.split(' ')[k] } // functor cu doua argumente // utilizare functor “word”str="unu doi trei patru "for (k in 0..3) println word(str,k) // unu,doi,trei,patru

Pentru a introduce o nouã metodã “word” în clasa “String” trebuie sã adãugãm metaclasei un functor, care va actiona asupra obiectului pentru care se apeleazã metoda (“delegate”):

String.metaClass.word={ delegate.split(' ')[it] } // utilizarefor (k in 0..3) println str.word(k) // unu,doi,trei,patru

4

Page 5: Curs2 C#

Aplicatii uzuale pentru functori

Functori cu rol de functii callback

Rolul unei functii callback în programare poate fi ilustrat prin functia de comparare de obiecte care trebuie transmisã unei functii de ordonare (sau de cãutare într-o colectie ordonatã): definitia functiei de comparare depinde de tipul obiectelor comparate dar ea poate avea acelasi prototip indiferent de tipurile comparate (folosind tipul generic Object în Java sau void* în C).Pentru a avea o singurã functie de sortare pentru orice listã (cu date de orice tip) vom transmite acestei functii ca argument (callback) functia de comparare. Exemplu de ordonare descrescãtoare a unui vector de obiecte în Java:

Arrays.sort (a, new Comparator() { // definire clasa anonima public int compare (Object o1, Object o2) { // cu o singura functie Comparable c1=(Comparable)o1; Comparable c2=(Comparable)o2; return c2.compareTo(c1); } });

In Groovy metoda "sort" se poate aplica oricãrui obiect de tip Collection, are ca rezultat un obiect de tip List si poate avea ca argument un obiect de tip Comparator sau un functor. Varianta cu argument Comparator:

list=['unu','doi','trei'] println list.sort() // crescator println list.sort (new Comparator() {int compare(a,b){ b.compareTo(a)} })

Varianta cu functor este mai scurtã si este în spiritul limbajului Groovy:

println list.sort {a,b -> b.compareTo(a)}

In cazul unei liste de numere se poate face comparatie prin scãdere:

list=[4,2,8,6] list.sort { a,b -> b-a}

In Groovy se poate folosi si metoda "reverse" din clasa List:

list.sort().reverse() // pentru orice tip de date

In Groovy metodele care pot avea ca parametru (si) un functor sunt fie metode noi, fie metode Java care au fost supraîncãrcate cu o noua formã.

Functori ca actiuni repetate într-un ciclu sau într-o enumerare

Primul exemplu este solutia alternativã Groovy pentru programarea unui ciclu prin introducerea în clasa Number a unor metode noi cu argument functor:times(Closure cl), upto (Number to, Closure cl), each (Closure cl). Exemple:

5

Page 6: Curs2 C#

// calcul factorial 5! nf=1(1..5).each {nf *= it } // varianta 11.upto(5) {nf *=it} // varianta 2

De observat cã solutia urmãtoare va produce rezultatul zero deoarece metoda "times" repetã functorul pentru valori 0,1,2,.. 5.times {nf *= it }

Alt exemplu de repetare a unei actiuni cu metoda "upto":

boolean prim(int n) { // definire functie prim este=true 3.upto(n-1){ if(n%it==0) este=false} return este}5.upto(50){ print prim(it)? it+' ':''}

Deseori apare situatia în care un vector (intrinsec) sau o colectie de obiecte este parcurs pentru a prelucra elementele colectiei sau pentru a cãuta un anumit obiect sau pentru a selecta obiectele care satisfac anumite conditii. Pentru fiecare element din colectie se executã o actiune (calcule, comparatii). In Java se foloseste de obicei un iterator pe colectie sau un ciclu cu regãsire prin indici asociati elementelor colectiei (numai pentru liste). Ex:

for (Iterator it=list.iterator(); it.hasNext();) { Integer x = (Integer) it.next(); System.out.println ( x%2==0 ? x+" par" : x+" impar"); }

Incepând cu Java 5 este simplificatã iterarea pe o colectie genericã. Ex:

for (Integer x: list) System.out.println ( x%2==0 ? x+" par" : x+" impar");

In practicã existã anumite prelucrãri tipice pe colectii : cãutarea primului obiect sau tuturor obiectelor care satisfac anumite conditii, aplicarea acelorasi operatii (transformãri) fiecãrui element din colectie, pe loc sau cu crearea unei noi colectii cu rezultatele operatiilor. Pentru aceste prelucrari s-au definit în Groovy noi metode aplicabile tuturor colectiilor: find, findAll, each, collect, sum s.a. Aceste metode primesc ca argument un functor ce defineste fie criteriul de cãutare, fie operatia aplicatã elementelor colectiei. Argumentul implicit din functor desemneazã elementul curent din colectie. Exemple:

int[] a =[1,2,3,4,5,6,7,8,9]a.each { println " $it ${it%2==0?'even':'odd'}" }a.findAll { it % 2 } // scrie numerele impare (1 3 5 7 9) Anumite metode din interfata Java "Collection" au fost supraîncarcate cu o formã care admite ca argument un functor. Exemplu de eliminare a duplicatelor dintr-o listã :

a =[1,2,3,1,1,4,4,2]a.removeAll { a.count (it)>1 }println a.sort() // [ 1 2 3 4 ]

6

Page 7: Curs2 C#

Metoda "each" este aplicabilã si caracterelor dintr-un sir. Exemplu: 'abcd'.each { print it+' ' } // scrie: a b c d

In clasa File s-au introdus metode de enumerare a fisierelor dintr-un director (eachFile) si de enumerare a subdirectoarelor (eachDir) cu parametri functor. Exemplu de definire a unui functor recursiv:

def listFiles // declaratie necesara inaintea apelului recursivlistFiles = { println "Dir ${it}" it.eachDir (listFiles) // parametru nume de functor it.eachFile { // parametru functor definit ad-hoc if(! it.isDirectory()) println " File ${it}" }} // utilizarelistFiles ( new File ("d:/groovy"))

Variantã de functie pentru listare recursivã fisiere dintr-un director:

def files (File f) { f.eachFile { if( it.isDirectory()) files(it) else println it }}

In Groovy aceastã operatie se poate face mai simplu cu o noua metodã:

new File(".").eachFileRecurse {println it}

Metoda "eachLine" din clasa File repetã un functor pentru fiecare linie dintr-un fisier, incluzând deschiderea, inchiderea fisierului si tratarea eventualelor exceptii. Exemplu de afisare cu numerotare linii:

int k=1 new File("a.txt").eachLine { println k++ +' '+it } // { println "$k $it";k++}

Deoarece permit o exprimare mai naturalã a unor operatii (actiuni) functorii se folosesc în crearea de limbaje specializate DSL. Exemplu de script Groovy pentru o aplicatie simplã Swing folosind un DSL predefinit numit “Swing Builder”:

bldr= new groovy.swing.SwingBuilder()frame=bldr.frame ( title: 'Swing',size:[50,100],layout:new java.awt.FlowLayout()) { btn=button(text:'Clickme',actionPerformed: {btn.text= 'Clicked'})}frame.show()

7