Trebuie să mărturisesc de la bun început că sunt un începător în acest limbaj, că nu am scris nici o aplicaţie în Ruby. Mi-a fost semnalat de un prieten (mulţumesc, Teo), aşa că am răsfoit nişte prezentări pe Web, m-am jucat cu un interpretor, am citit o jumătate de carte... Lucruri pe care le face oricine când primeşte o jucărie nouă. Aşadar, aceasta prezentare este doar un semnal, o gustare (digerabilă într-o jumătate de oră) menită să vă transmită ceva din aroma acestui nou limbaj în vogă. Dacă vă place, o căutare pe Web vă oferă informaţii suplimentare. Punctele de plecare sunt www.ruby-lang.org şi www.rubycentral.com.
Ruby a fost creat de un programator japonez, pe nume Yukihiro Matsumoto (zis Matz), care, nemulţumit de toate celelalte alternative, s-a hotărât să-şi creeze un limbaj de programare propriu. Aceasta se întâmpla prin 1993, iar prima versiune a fost gata în 1995. Cu timpul, limbajul a început să capete popularitate. Mai întâi în Japonia natală (unde se pare că a depăşit Python-ul), apoi a început să se răspândească în lume. De curând a apărut prima carte despre Ruby în limba engleză, ceea ce, având în vedere calităţile limbajului, creează premisele unei răspândiri mult mai accelerate.
În general
O caracterizare minimală (repede, că ne grăbim la cod!) ar suna aşa:
Ruby este un limbaj de scripting;
Ruby este un limbaj pur obiectual;
Ruby este liber (open source).
Ar mai trebui spus că Ruby este un limbaj eclectic, în sensul că a preluat o multitudine de caracteristici preţioase din numeroase alte limbaje, dintre care două se disting în mod special: Smalltalk şi Perl. În ciuda acestui fapt, limbajul este simplu, consistent şi succint în exprimare.
Ca şi în Smalltalk (dar spre deosebire de Python), în Ruby nu există nimic altceva decât obiecte. Orice există este instanţă a unei clase. Ca şi în Smalltalk, orice acţiune se face trimiţând un mesaj unui obiect. Spre deosebire de Smalltalk, Ruby nu produce bytecode ci se interpretează.
În plus, sintaxa lui Ruby este mult mai familiară decât cea utilizată de Smalltalk, deşi este inspirată din Eiffel şi Ada, cu influenţe din Perl. Spre deosebire de acesta din urmă, Ruby este lizibil.
În fine, definiţia mea: Ruby este Smalltalk adaptat pentru scripting. Aceasta implică o sintaxa familiară, interpretare directă şi utilizarea fişierelor.
Salutare, lume
Caracterizări de ordin general găsiţi câte vreţi pe Web. Aşadar:
def salut(nume)
rez = "Salutare, " + nume
return rez
end
Prin convenţie, rezultatul evaluării va fi introdus în acest articol prin simbolul ==>>. Deci:
salut("lume") ==>> "Salutare, lume"
salut("Mircea").length ==>> 16
Nici o surpriză. De fapt, Ruby a fost construit pe baza unui principiu cât se poate de sănătos, cunoscut sub numele POLS (Principle of Least Surprise).
Aşadar, am definit o funcţie numită salut. De fapt, nu este o funcţie, ci o metodă. Atunci când receptorul nu este specificat este considerat implicit self, iar în cazul în care codul nu apare în cadrul unei clase, acesta este o instanţă generică a clasei Object, rădăcina ierarhiei de clase din Ruby. Aceasta este o veste bună pentru cei care nu se simt în largul lor în lumea obiectuală: se poate programa fără clase! De fapt, se lucrează în cadrul clasei Object, dar acest lucru este transparent.
Sintaxa este foarte liberală şi există mereu alternative. Pot scrie, de exemplu:
salut "lume" ==>> "Salutare, lume"
(salut 'Mircea').length ==>> 16
Pe de altă parte, metoda salut poate fi scrisă mult mai simplu (aici însă ghilimelele sunt importante, nu pot fi apostrofuri):
def salut(nume) "Salutare, #{nume}" end
Deci, nimic special până aici. Ruby dispune, desigur, de structurile de date tipice din limbajele de scripting: liste (array) şi dicţionare (hash), precum şi de structurile tipice de control (if, while, for etc.):
revista = 'NetReport'
a = ['Agora', 2000, "#{revista}"]
b = Hash.new
b['editura'] = a[0]
b[:an] = a[1] += 1
b['revista'] = revista
for k in b.keys
if k.kind_of? String
puts b[k]
end
end
Fără surprize, codul de mai sus va afişa:
Agora
NetReport
Codul arată cât de poate de tipic pentru scripting. Aproape totul este însă syntactic sugar, (adică "înlocuitor") pentru clasica tripletă obiectuală receptor.mesaj(argumente). Smalltalk deghizat. Listele, dicţionarele, variabilele, toate sunt instanţe ale unor clase. Până şi operatorii sunt de fapt mesaje. Ba chiar şi structurile de control! Iar enigmaticul :an este (ca şi în Smalltalk) un simbol (adică o instanţă a clasei Symbol).
Clase
Oricât de bogată ar fi ierarhia de clase de care dispune în mod nativ Ruby, aplicaţiile noastre vor avea nevoie de clase specifice. Desigur, avem posibilitatea să le definim. Iată un exemplu:
class Persoana
def initialize(prenume, nume, sex, loc)
@nume = nume + ' ' + prenume
@sex, @loc = sex, loc
end
end
Clasa pe care am definit-o se numeşte Persoana. Ca şi în Smalltalk, există convenţii în privinţa identificatorilor, prin care se desemnează de fapt domeniul lor de vizibilitate (nu tipul, ca în Perl). Variabilele locale încep cu minusculă, cele globale sunt prefixate cu caracterul $, numele claselor şi al constantelor încep cu majusculă, variabilele instanţelor se prefixează cu @ iar variabilele de clasă cu @@. Se poate observa în cod faptul că datorită acestei convenţii, Ruby distinge variabilele corespunzând argumentelor de variabilele instanţei (deci nu este nevoie de self).
Singura metodă definită, initialize, are o semnificaţie specială, fiind ceea ce s-ar putea numi, printr-un uşor abuz de limbaj, un constructor. Mai precis, este metoda care răspunde la mesajul new adresat clasei (desigur, clasele sunt şi ele obiecte) şi creează o instanţă:
pers = Persoana.new('Mircea', 'Sarbu', :m, "Tg-Mures")
Dacă vom încerca să afişăm persoana pers, vom obţine ceva de genul #<Persoana:0x46222e8>, ceea ce nu ne edifică prea mult. O soluţie ar fi să definim o metodă care să afişeze nişte informaţii inteligibile despre persoană. Dar putem face şi altceva: clasa Object dispune de o metodă numită to_s care produce o reprezentare textuală a obiectelor. Această metodă este moştenită de toate clasele, inclusiv de Persoana (care, în mod implicit, este o subclasă a clasei Object). De fapt, această metodă a produs şirul de caractere de mai sus. Avem posibilitatea să o redefinim:
class Persoana
def to_s
form = 'Dl'
form = 'Dna' if @sex == :f
"#{form}. #{@nume} (#{@loc})"
end
end
Sunt sigur că iubitorii de Perl au o tresărire de simpatie la vederea acestui cod. Pentru iubitorii de Python, o consolare: se poate scrie şi mai frumos...
În primul rând, să remarcăm că o clasă este mereu deschisă, în sensul că putem oricând să o modificăm (în cazul acesta i-am adăugat o metodă). Apoi să observăm forma alternativă pentru if când există o singură ramură.
Ce facem însă dacă dorim să obţinem doar numele unei anumite persoane? Dar dacă persoana se mută în altă localitate? Avem nevoie de metode care să permită accesul la anumite variabile ale instanţelor. În mod normal acestea sunt destul de simple. De pildă:
class Persoana
def loc= (loc)
@loc = loc
end
end
Simple, dar multe. Observaţi că de fapt se redefineşte operatorul de atribuire.
Din fericire, avem o cale simplă:
class Persoana
attr_reader :nume, :sex
attr_accessor :loc
def numeNou(prenume, nume)
@nume = nume + ' ' + prenume
end
end
Am declarat astfel că variabilele de instanţă nume şi sex pot fi citite direct, iar atributul loc poate fi scris şi citit direct. Există şi varianta attr_writer, pe care nu am folosit-o. În schimb am declarat o metodă proprie pentru schimbarea numelui. Acum putem face atribuiri de genul:
pers2 = pers.clone
pers2.loc = "Bucuresti"
pers2.numeNou('Ion', 'Ionescu')
n = pers.nume
Dacă intenţionaţi să evitaţi crizele de nervi, reţineţi că toate aceste aşa-zise "declaraţii" sunt de fapt comenzi executabile, aşa că în cadrul aceleiaşi sesiuni o schimbare în structura unei clase poate genera conflicte cu declaraţiile anterioare. Ruby e un interpteror, nu un compilator!
Iată şi moştenirea:
class Angajat < Persoana
@@nr = 0
Limita = 100
def initialize(p, salar)
@salar, @loc = salar, p.loc
@marca = @@nr += 1
np = p.nume.split(/\W+/)
@nume = np[0] + ' ' + np[1]
end
def to_s
super.to_s + " marca= #{@marca}"
# salariul e confidential
end
def Angajat.preaMulti?
return @@nr > Limita
end
end
Desigur, am forţat puţin nota pentru a ilustra pe scurt câteva aspecte:
Variabila de clasă @@nr va fi incrementată la fiecare creare a unei instanţe, astfel încât angajaţii vor primi mărci unice.
Ruby dispune de întregul mecanism de expresii regulate.
Referirea la metodele clasei părinte se fac prin variabila super.
Constantele poartă nume care încep cu majusculă.
Metodele de clasă poartă nume prefixate cu numele clasei.
Folosind şi codul pentru exemplificarea clasei Persoana, putem scrie:
a1 = Angajat.new(pers, 21000)
a2 = Angajat.new(pers2, 12000)
puts a2.to_s
==>> Dl. Ionescu Ion (Bucuresti) marca= 2
puts Angajat.preaMulti?
==>> false
Iteraţii
O altă caracteristică pe care Ruby a preluat-o direct din Smalltalk este posibilitatea de a furniza unei metode un bloc. Un bloc cuprinde o porţiune de cod cuprins între acolade sau între do şi end. Iată un exemplu:
[1, 3, 7].each { | x | print x * x, ' ' }
==>> 1 9 49
Metoda each aparţine clasei Array (de fapt tuturor colecţiilor) şi am putea crede că e ceva magic în ea. Nu este nimic altceva decât metoda yield, care furnizează argumente blocului pe care metoda îl va primi (blocul din exemplu dispune de parametrul formal x, declarat între bare verticale). De fapt, putem scrie o metodă proprie pentru aceasta:
class Array
def fiecare
for x in self
yield x
end
end
end
[1, 3, 7].fiecare do
| x |
print x * x, ' '
end
==>> 1 9 49
Lucrurile se petrec exact ca în Smalltalk. Un bloc este o secvenţă de cod a cărei execuţie este amânată. Blocul trebuie să înceapă pe aceeaşi linie pe care se termină metoda, deoarece interpretorul îl citeşte şi-l memorează înainte de a evalua metoda. După ce l-a memorat, interpretorul evaluează metoda şi, de câte ori întâlneşte metoda yield, evaluează blocul.
O metodă urmată de un bloc de numeşte iterator. Putem să ne definim iteratorii pe care îi dorim, în funcţie de specificul problemei. Iată, spre exemplu, o posibilă utilizare într-o clasă desemnată să cuprindă angajaţii care lucrează într-un departament.
Înainte de a defini clasa Departament, voi completa clasa Angajat cu o metodă de sortare:
class Angajat
def <=> (unAng)
return @nume <=> unAng.nume
end
end
Am redefinit operatorul de comparaţie <=> astfel încât să compare doi angajaţi prin numele lor. Operatorul <=> returnează -1 (mai mic), 0 (egal) şi 1 (mai mare), fiind implicit folosit de metoda sort a clasei Array.
Acum, clasa Departament :
class Departament
def initialize
@angajati = Array.new
@nr = 0
end
def <<(unAng)
@angajati << unAng
@nr += 1
end
def parcurge
for x in @angajati.sort
yield x
end
end
end
Operatorul << adaugă elemente într-o listă (este echivalent cu metoda Array::push). L-am redefinit pentru a funcţiona şi pentru departamente.
Putem acum să adăugăm angajaţi astfel:
marketing = Departament.new
marketing << a1
marketing << a2
marketing << Angajat.new(
Persoana.new("Adriana", "Radu", :f, "Ploiesti"),
19000)
Acum putem să obţinem, de exemplu, lista angajaţilor cu salariul mai mare de 15.000:
marketing.parcurge do
|a|
puts a.nume if a.salar >15000
end
Altele
Ruby mai dispune de multe alte caracteristici extrem de interesante. Spaţiul tipografic (precum şi experienţa mea limitată cu Ruby) nu-mi permit să le prezint pe toate, dar o scurtă enumerare poate fi edificatoare:
Tratarea excepţiilor (similar cu Python)
Fire de execuţie (implementate independent de thread-urile sistemului de operare gazdă!)
Module şi mixins (acestea din urmă permit o formă sigură de moştenire multiplă)
Mecanisme reflexive (permit examinarea dinamică a caracteristicilor obiectelor)
Colectare automată a memoriei reziduale (garbage collection)
Implementează direct câteva "tipare software" (software patterns): visitor, singleton, observer, delegation
Dispune de biblioteci bogate
Posibilităţi de extensie
Merită să-l încercaţi, mai ales că găsiţi pe Web, alături de software, şi conţinutul complet al cărţii Programming Ruby (The Pragmatic Programmer's Guide) de Dave Thomas şi Andy Hunt.