Let’s make things Groovy

Jakiś czas temu przeczytałem artykuł (http://steve-yegge.blogspot.com/2007/12/codes-worst-enemy.html) w którym autor dość nieprzychylnie wyrażał się o Javie jako języku programowania. Mimo, że nie całkiem zgadzam się ze wszystkim co zostało tam napisane to jednak zacząłem zwracać uwagę na to ile kodu wymaga Java nawet do najprostszych rzeczy. Zerknijmy sobie na prosty przykład – bardzo standardowe „Hello World”:

public class Main {
  public static void main(String[] args) {
    System.out.println("Hello World!");
  }
}

Już patrząc na samą instrukcję System.out.println() odnosi się wrażenie, że to jest dużo pisania jak na tak prostą funkcję. Dla porównania możemy spojrzeć na kod Pythona:

print "Hello World"

Super! Krótko zwięźle i na temat. Problem w tym, że my znamy się dobrze na Javie i potrafimy używać przeróżnych bibliotek do tego języka, a w dodatku nie znosimy nazewnictwa metod w takiej_postaci() preferując raczej postaćTakową(). No dobra, może nie wszyscy mają takie uprzedzenia ale ja na pewno tak 🙂 (na pewno nie wydaje się wam, że Python ma za dużo __podkreśleń__?). A jeśli to nie jest problem to może brak standardu dla dokumentacji Pythonowej? Lub to, że część bibliotek dla Pythona dokumentacji nie ma wcale lub sprowadza się ona po prostu do listy wszystkich klas i metod, które czasem są opatrzone lapidarnym komentarzem? (mówię o czymś w stylu: get_none() – w dokumentacji przeczytamy returns None…)

Jeśli chcecie teraz zaproponować mi Jythona to dajcie sobie spokój. Byłem, sprawdziłem, nie podoba mi się 🙂 Najbardziej odrzuca mnie czas startu ale samo tempo pracy również nie powala. Możecie mi wierzyć, że bardzo chciałem sprawić by Python stał się moim językiem ojczystym do programowania, ale ostatecznie zawsze wracałem do Javy.

Potem odkryłem Groovy

O Groovy słyszałem już wcześniej. Nikt jednak nie wydawał się podekscytowany tym językiem i nikt nie zwracał na niego większej uwagi. W takim razie, pomyślałem, ja też nie będę. Ostatnio jednak w wyniku poszukiwania języka innego niż Java ale działającego na JVM (chodziło mi o kompatybilność z bibliotekami) postanowiłem zabrać Groovy na jazdę testową.

To była chyba najbardziej inspirująca zabawa z kodem odkąd zacząłem programowanie! W ciągu weekendu przeczytałem wszystkie interesujące mnie rzeczy dotyczące języka (głównie – co jest tu innego niż w Javie, wszelkie metaprogramowanie oraz ciekawostki na które się natknąłem jak np. DSL w Groovy). Najciekawsze jednak było to, że czułem się jakbym odkrył nowe możliwości w czymś co znam już od dawana. Pisałem po prostu w Javie ale wykorzystywałem dodatkowe możliwości, które zupełnie zmieniły styl kodu i wygodę programowania.

Co jest wartego uwagi w Groovy?

Przede wszystkim – jest to język dynamicznie typowany, choć trzeba zaznaczyć, że jest to nieco ciekawsze niż w przypadku Pythona gdyż możemy podawać typy analogicznie jak w Javie:

// Przykład 1
def greet(String who) {
  println "Hello $who!"
}

def greet2(who) {
  println "Hello $who!"
}

greet("Groovy")
greet2 "Groovy"
greet2 12345

W tym przykładzie widać kilka nietypowych dla Javy konstrukcji, ale zacznijmy od tematu omawianego wcześniej. Mamy zdefiniowane dwie funkcje. Pierwsza z nich posiada zastrzeżenie, że argument who musi być typu String. Próba wywołania takiej metody z argumentem innego typu skończy się wyrzuceniem odpowiedniego wyjątku (oczywiście w czasie wykonania programu). Druga funkcja nie posiada takiego ograniczenia i w tym wypadku możemy podawać właściwie dowolne obiekty. Oczywiście możemy również określić typ zwracany przez funkcję podając typ pomiędzy słowem kluczowym def a jej nazwą.

Dodatkowo w funkcjach i metodach nie musimy używać słowa return (zazwyczaj) gdyż ostatnia wartość jest tą zwracaną. To oznacza, że jeśli napiszemy funkcję, która kończy się linijką zawierającą po prostu liczbę: 25 to ta liczba będzie wartością całej funkcji (chyba, że wcześniej użyto słowa kluczowego return).

Kolejne niespodzianki czekają na nas już w linii numer 2! Zamiast przydługiego System.out.println mamy po prostu println. Mimo, że bardzo przypomina to trochę rozwiązanie zastosowane w języku Python wcale takie nie jest. Tak na prawdę wywoływany jest stary dobry System.out.println. Groovy przy starcie globalnie importuje statycznie wspomnianą metodę. Efekt jest dokładnie taki jakbyśmy napisali na początku każdej klasy Javy:

import static System.out.println;
// wówczas można tej metody używać tak:
println("Hello World");

Dodatkowo Groovy automatycznie importuje kilka przydatnych rzeczy do każdego kompilowanego pliku:

import java.lang.*;
import java.util.*;
import java.net.*;
import java.io.*;
import java.math.BigInteger;
import java.math.BigDecimal;
import groovy.lang.*;
import groovy.util.*;

Idąc dalej w drugiej linijce kodu zauważymy, że argument tej metody nie znajduje się w nawiasie. Jest to oczywiście uproszczenie wprowadzone w Groovy, żeby pozbyć się niepotrzebnych znaków z kodu przez co ma on stać się czytelniejszy. Oczywiście nic nie stoi na przeszkodzie, żeby nawiasów używać. Możemy zatem napisać: println(„Hello World”). Jedyny zgrzyt może pojawić się gdy chcemy wywołać funkcję lub metodę nie posiadającą argumentów. Wówczas kod wygląda identycznie jak dostęp do pola klasy, a jeśli Groovy go nie znajdzie to rzuci wyjątkiem informującym, że takiego pola nie ma. Ja preferuję pisanie nawiasów wszędzie (z wyjątkiem println [nie wiem czemu]) dlatego, że w innym wypadku moim zdaniem kod wygląda niespójnie.

Kontynuując naszą podróż po linijce numer 2 natrafimy na $who w środku stringa. Jest to kolejny cukierek od Groovy. Możemy w ten sposób wstawiać dowolne wartości do ciągu znaków. Co jest jeszcze ciekawsze? Oczywiście to, że dodając klamry możemy w ten sposób wykonać w międzyczasie dowolny kod Groovy:

println "Test: ${def b = 30; ++b}"
// Wyświetli: "Test: 31"

Docierając do końca 2 linijki możemy zauważyć brak średnika. Jest on oczywiście opcjonalny. Niektórym się to podoba, innym nie, ale to nie ma większego znaczenia – każdy pisze jak mu się podoba 🙂 Warto jednak pamiętać, że średnik rozdziela instrukcje i, tak jak w powyższym przykładzie, czasami może się przydać.

Coś więcej?

Hmm… znajdzie się więcej 🙂 Prawdziwą siłą Groovy są domknięcia (ang.: Closures – tak po polsku brzmi jakoś głupawo). Domknięcie jest, ujmując to najprościej, kawałkiem kodu, który nie ma nazwy i może być wywołany zupełnie jak funkcja. Deklaruje się je przy pomocy klamer {}. Do czego można je wykorzystać? Całkiem fajny przykład podał Andrew Montalenti w swoim poście i pozwolę sobie go przytoczyć:

["Rob", "Christopher", "Joe", "John"].findAll{ 
  it.size() < = 4 
}.each { 
  println it 
}
// ==> prints "Rob", "Joe" and "John"

Groovy dodaje kilka przydatnych metod do pracy na tablicach i listach (m. in. findAll i each). Być może niektórym z was przypomina to trochę jQuery – i dobrze 🙂 W tym przykładzie po prostu mówimy, że chcemy znaleźć wszystkie elementy tablicy, których długość jest mniejsza od 4, a potem każdy z nich wyświetlić. Tak na prawdę ten opis jest zupełnie zbędny bo nawet źrednio rozgarnięty programista nawet nie znający Groovy zrozumie o co tu może chodzić 🙂

Closures w Groovy są traktowane trochę specjalnie. Jeśli metoda jako ostatni argument posiada właśnie domknięcie to może ono być podane poza nawiasem tej metody. To znaczy, że mając:

// metoda wywoluje domknięcie z parametrem s
def metoda(String s, Closure c) {
	c(s);
}

Możemy użyć takiej metody na dwa sposoby:

metoda("hello", { println it })

metoda("hello") { println it }

Polecam jednak odwiedzić stronę Groovy i poczytać więcej. Jeśli ktoś zna już Javę to nauka Groovy jest stosunkowo bezbolesna. Właściwie jest to wręcz przyjemność bo możemy zamienić często długi kod javy na krótsze odpowiedniki w Groovy.

A wydajność?

Są pewne argumenty przeciw używaniu Groovy. Jak każdy język dynamiczny posiada on pewien narzut obliczeniowy, który powoduje, że jest on wolniejszy niż zwykła Java. Jak dotąd w celu zwiększenia wydajności pewnej częśći kodu można było użyć adnotacji @CompileStatic. Niestety użycie jej mogło spowodować, że kod zachowa się nieco inaczej niż byłoby to w Groovy.

Na nasze szczęście przyszła Java 7, a wraz z nią JVM uzyskał nową funkcję o nazwie InvokeDynamic. Powinna ona pozwolić Groovy na znaczne zbliżenie się do wydajności Javy. Na razie nie ma tego wiele, ale niektórzy twierdzą, że jego wykorzystanie faktycznie poprawiło znacząco predkość.

Nie obiecuję, że jeszcze napiszę coś o Groovy bo zawsze kiedy coś podobnego obiecuję to ostatecznie i tak tego nie robię 🙂 Dlatego po prostu miejmy nadzieję, że się uda 😉

Reklamy