Tri saveta Rubi početniku

Pratim prilično redovno pitanja sa Ruby tagom na StackOverflow, i primetio sam da se dobar deo pitanja vezanih za Ruby kao jezik vrti oko načina za manipulaciju podacima smeštenim u osnovne Rubi strukture podataka - često postavljač pitanja okači svoje “naivno” parče koda i pita kako bi izgledalo “elegantnije” ili “rubističkije” rešenje.

S tim u vezi, evo par saveta za Rubi početnika koji želi da što pre prevaziđe ovakva pitanja, koja se zapravo svode na pisanje Java ili PHP koda u Rubiju:

  1. Naučite jako dobro čemu služe i kako rade metode Enumerable#map i Enumerable#inject!
  2. Prođite nekoliko puta kroz dokumentaciju za Enumerable, Array i Hash, pažljivo pročitajte koje metode nude i koje sve oblike te metode mogu da uzmu (neke primaju opcioni blok itd).
  3. Naučite šta su blokovi. Naučite u čemu se razlikuju od funkcija, kako se prosleđuju i pozivaju iz metoda. Naučite pojam closures.

Postoji još nekoliko “alata” koje Rubiju daju prednost nad “običnim” jezicima, ali se za početak zadržite na ove tri stavke, dok ih ne usvojite u potpunosti.

Je li f()==5 i 5==f() isto?

Vi koji ste radili sa C-olikim jezicima setićete se jedne od konvencija pri pisanju if-uslova, da se vrednost sa kojom se promenljiva poredi piše sa leve strane znaka jednakosti, a promenljiva s desne (if 5 == i naspram uobičajenog if i == 5), zbog česte greške da se umesto dvostrukog znaka jednakosti stavi jednostruki, i odradi dodela vrednosti promenljivoj s leve strane, za šta se neki kompajleri ne bi bunili.

Samo naoko sličan problem javlja se ponekad i u Rubiju, ali ne zbog moguće greške u kucanju, već zbog prirode znaka jednakosti.

Analizirajući performanse jedne skripte profajlerom, primetio sam da se u listi pozivanih metoda neočekivano javlja metoda Time#<=> (koja opet, iz ko zna kog razloga, zove BasicObject#method_missing), iako sam bio prilično siguran da u kodu nemam poređenje objekata klase Time. Detaljnijim pregledom shvatio sam da se poziv te metode javlja pri proveri rezultata jedne metode, tj. u otprilike ovakvom koodu:

return if m() == false

(Pre nego što počnete prozivku zbog onog == false, reći ću da mi je bila potrebna eksplicitna provera za false, tj. nisam hteo da mi se uslov ispuni ako je rezultat metode nil, već samo false.)

Ispostavilo se da metoda m u nekim slučajevima vraća objekat klase Time. (Ok, još malo pravdanja: metoda m je zapravo metoda send na jednom objektu, i može zapravo pozivati različite metode, otud nije baš uvek izvesno šta će se naći kao vraćena klasa.) S obzirom da je u Rubiju manje-više sve ili objekat ili poziv metode nekog objekta, ono == je takođe poziv metode. U slučaju klase Time, ovaj operator ne vrši jednostavno poređenje referenci, pa se usled toga == kome je poziv metode sa leve strane različito ponaša od onog gde je sa leve strane npr. false. Ok, da ne grešim dušu, kood se ponaša isto, ali se izvršava različitom brzinom, što opet nekad može da bude bitno.

Da bi se izbegla nepotrebna logika vezana za konkretnu implementaciju metode == levog objekta, dovoljno je promeniti redosled:

return if false == m()

false je u Rubiju instanca klase FalseClass, i njeno == je isto što i obično Object#==, odnosno jednostavna provera jesu li u pitanju isti objekti.