18 stycznia 2009

Wyrażenia regularne, przeciążanie operatorów i operatory specjalne w Groovy z "Beginning Groovy and Grails"

Na zakończenie rozdziału 2. "Groovy Basics" z Beginning Groovy and Grails: From Novice to Professional autorzy omawiają temat obsługi wyrażeń regularnych w Groovy. Wyrażenie regularne jest łańcuchem znakowym (symboli), który tworzy wzorzec, za pomocą którego odszukuje się pasujących ciągów znakowych w tekście. Nie zauważyłem różnic w języku wyrażeń Groovy a Javą, więc zakładam, że ich po prostu nie ma. Mam wrażenie, że w języku dynamicznym, takim jak Groovy, znaczenie wyrażeń regularnych znacznie wzrasta. Policzyłbym na palcach jednej ręki, ile razy przyszło mi skorzystać z wyrażeń regularnych w moich programach w Javie, podczas gdy w skryptach uniksowych bądź edycji w vi jest ich cała masa i nie tylko, że jednej, ale dziesięciu rąk nie starczyłoby na ich zliczenie. Wierzę, że praca z Groovy zmieni moje podejście do wykorzystania wyrażeń regularnych w Javie. Uczestniczę w projekcie Apache OpenEJB, w którym David Blevins, główny programista projektu, stosuje wyrażenia regularne do najróżniejszych, często zaskakujących, zadań. Główną różnicą między pracą z wyrażeniami regularnymi w Groovy a Javą są nowe operatory porównania ==~, szukania =~ oraz (konstrukcji) wzorca ~ciąg, w których "cięte ciągi" (ang. slashy strings) znacząco upraszczają pracę, np.
 def wzorzec = ".*c\$"
def wzorzecJakWyzejAleProsciej = /.*c$/
Wynikiem działania operatora szukania jest java.util.regex.Matcher[][], zaś operatora wzorca java.util.regex.Pattern. Autorzy zwracają uwagę na operatory ~ oraz =~, ponieważ wystarczy zapomnieć o spacji i ze wzorca
 def wzorzec = ~/.*c$/
mamy operator szukania
 "abc" =~ /.*c$/
W jednym z przykładów można spotkać się z metodą tworzenia obiektów przypisując wartości polom przez nazwę.
 class Osoba {
String imie
}

def jacek = new Osoba(imie: "Jacek")
Pamiętam podobną konstrukcję w C++, gdzie definiując konstruktor można było określić wartości domyślne dla pewnych pól instancji.

Temat operatorów i możliwości ich przeciążania jest zamknięciem rozdziału 2. Przeciążanie operatorów nie jest dostępne w Javie, ale programiści C++ znają to nadzwyczaj dobrze. W Groovy definiujemy pewną ustaloną metodę, która będzie wykonywana przy wykonaniu operatora, np. dla a + b będzie to a.plus(b). Jeśli jej brak w naszej klasie będzie wykonywana domyślna obsługa operatora. Proste, nieprawdaż? Już nie pamiętam, aby tak prosto było w C++, ale na pierwszy rzut oka Groovy sprowadza temat przeciążania operatorów do banału. Dodanie elementu do listy to wykonanie lista.add(obiekt), albo, z wykorzystaniem przeciążania, lista << obiekt (co odpowiada wykonaniu metody lista.leftShift(obiekt)). Zdaje się, że ostatnie zdanie w sekcji Operator Overloading "Operator overloading isn't limited to..." sugeruje, że istnieje możliwość definiowania własnych operatorów, ale poza tą wzmianką nic więcej nie ma, więc przyjdzie mi dalej żyć w błogiej niewiedzy, czy mógłbym zdefiniować własny operator czy nie.

Groovy udostępnia kilka specjalizowanych operatorów - spread - *. (ang. spread operator), który jest uproszczeniem wykonania metody lub domknięcia na poszczególnych elementach kolekcji, które udostępniają wywoływaną metodę/domknięcie.
 def lista = ["Agata", "Iweta", "Patryk", "Jacek"]
lista.each { println it }
lista*.length()
Można go interpretować jako "wykonaj metodę/domknięcie na każdym elemencie listy".

Kolejnym operatorem jest Elvis - ?:, który jest uproszczonym operatorem warunkowym ?: znanym z Javy. W Javie mamy trzy składowe, podczas gdy w Groovy wyłącznie dwa i przypisanie wartości (wykonanie operatora Elvis) następuje wyłącznie, kiedy przypisywane pole ma wartość null lub false.
 // jeśli osoba.imie jest null zmienna imieOsoby będzie miało wartość "Nieznane"
def imieOsoby = osoba.imie ?: "Nieznane"
Kolejny operator specjalny to operator bezpiecznego odczytu - ?., który zapobiega NPE (NullPointerException). Zamiast
 if (osoba != null) {
println "${osoba.imie}"
}
wystarczy
 println "${user?.imie}"
Następny operator to operator bezpośredniego dostępu do pola z pominięciem wykonania metody odczytu (ang. getter) - .@, np.
 class Osoba {
String imie

def getImie() {
imie + " (getImie)"
}
}

def jacek = new Osoba(imie: "Jacek")
println jacek.imie
println jacek.@imie
Trudno uwierzyć mi, że może być zainteresowanie na tego typu konstrukcje, które łamią zasady programowania obiektowego - kapsułkowanie (ang. encapsulation) - ale skoro są, to powód ich powstania też musi być. To jest zaleta poznawania nowych języków - człowiek poznaje tym samym powody istnienia rzeczy, o których nie miał nawet pojęcia, że mogłyby istnieć. Nazwalibyśmy to osobistym rozwojem, czy zaśmiecaniem sobie głowy rzeczami, które są niewielkiej wartości?!

Ostatnim operatorem jest operator wykonania domknięcia - .&, który umożliwia potraktowanie dowolnej metody jako domknięcia, np.
 String wyswietl(String tekst) {
println tekst
}

def laskowscy = ["Agata", "Jacek"]
laskowscy.each(this.&wyswietl)
W ten sposób działa w Groovy println, który jest niczym innym jak wykonaniem javowego System.out.println.

Koniec rozdziału 2. "Groovy Basics".

p.s. Zgłosiłem blog do konkursu Blog Roku 2008. Zainteresowani wyrażeniem swojej bezgranicznej wdzięczności za literacką twórczość Jacka w jego Notatniku proszeni są o wysłanie SMSa o treści B00204 (czytaj: be-zero-zero-dwa-zero-cztery) pod numer 7144 za 1,22PLN brutto. Dziękuję!