Aneb jak a proč Groovy

autor: Václav Pech

Nedávno jsem měl možnost seznámit se s
programovacím jazykem Groovy a musím říct, že mě
nadchl. V tomto článku bych se rád podělil o své
dojmy s ostatními vývojáři, pokusil se vysvětlit, co
zajímavého přináší programovací jazyk Groovy do světa
Javy a proč stojí zato se o Groovy něco dozvědět. Jde
o volné a nenáročné úvodní představení filozofie a
principů Groovy, včetně několika motivačních ukázek
kódu, bez snahy o absolutní úplnost.

Osoby vystupující v příběhu a jejich jména jsou
smyšlené, případná podobnost s žijícími i nežijícími
osobami je veskrze náhodná. Ukázky kódu použité v
příběhu jsou pravé a funkční.

Stalo se jednoho nedávného dne v
jedné softwarové společnosti (záznam z IM historie)

Franta: Čau, jak je?

Bohouš: Nazdar! Prima, a co Ty?

Franta: Pohoda. Máš chvilku?
Potřeboval bych trochu helpnout.

Bohouš: O co 'de? Včera jsme
releasovali, tak mám teď celkem volno.

Franta: Rozjíždím nový projekt a
hodil by se mi někdo, jako jsi ty. Nejdřív bys nám
mohl napsat v Groovy takový …

Bohouš: Aha?! No to bude asi
problém, o Groovy nic nevím.

Franta: To se rychle naučíš, je
to pohoda.

Bohouš: Přece nebudu na starý
kolena utíkat od Javy!

Franta: Jak utíkat? Groovy je
budoucnost Javy.

Bohouš: Loni to na mě zkoušel
Pepa, že prý Ruby je budoucnost.

Franta: To měl docela pravdu.

Bohouš: Houby pravdu, vůbec jsem
tomu jeho kódu nerozuměl, taková náhodná změť znaků.
S Javou to neintegruje, na JBossu to nepustíš, žádné
pořádné IDE to nemá, prostě všechno znova, jak když
jsem začínal s Javou někdy v minulým století.

Franta: Ruby se rychle vyvíjí,
teď už s Javou docela integruje a i ta IDEčka už
jsou. Poslechni si CZ podcast číslo 15.

Bohouš: OK, ale ty po mně chceš
Groovy.

Franta: Groovy je takové
"Ruby v Javě". Syntaxe Groovy je
nadmnožinou syntaxe Javy, tedy zatím s několika málo
výjimkami, ale to se brzy spraví. Takže můžeš dál
psát Java kód, akorát to ukládáš do .groovy souborů a
je z tebe Groovy junior!

Bohouš: Fajn, ale to asi nebude
stačit pro ten tvůj projekt, co?

Franta: Groovy syntaxi se můžeš
učit postupně. Pro Java vývojáře je to celkem snadná
cesta do světa dynamických jazyků.

Bohouš: A co integrace s
existujícím Java kódem?

Franta: Bez obav. Groovy kód
můžeš normálně míchat s Java kódem v jednom projektu,
z Groovy volat Javu, z Javy volat Groovy, extendovat
či implementovat napříč oběma jazyky. Samozřejmě máš
v Groovy k dispozici celé známe JDK, přilinkovat
můžeš Java knihovny, takže dál klidně používej log4j,
hibernate, spring, prostě co chceš a na co jsi
zvyklý. Knihovny se nemusí přepisovat pro Groovy. OK?

Bohouš: OK. A co JBoss?

Franta: Groovy kompiluje do Java
bytecode, klidně piš Groovy i pro EE aplikačky.
Například v Seam 2.0 můžeš teď psát komponenty v
Groovy.

Výhody syntaxe Groovy

Bohouš: Takže s Groovy můžu
krásně žít uvnitř Java světa, ale proč tedy jiná
syntaxe?! Já v Javě napíšu všechno. Nevidím důvod,
proč bych potřeboval nový jazyk. Abych ušetřil pár
řádků kódu? Dneska ti všechna IDEčka kompletují nebo
generují kód, ani nemají problém nepotřebné části
schovat.

Franta: Koukni na tohle:

    company.employees.grep{it.age > 30}.name

Odhadneš, co ten kód dělá? Napovím ti, že kód ve
složených závorkách se nazývá closure a technicky je to
metoda předaná jako parametr metodě grep.
Identifikátor označený it zastupuje v kódu
closury její jediný parametr.

Bohouš: Vyjede všechny starouše
ve firmě, tedy jejich jména.

Franta: No, fajn. Ten kód jasně
říká, co dělá, ne jak to dělá. Takové traverzaci přes
několik objektů a kolekcí v Groovy se říká GPath. Jak
bys to napsal v Javě?

Bohouš: Jednoduše. Iteroval bych
přes ty employees, u každého bych vyhodnotil podmínku
na age a případně ho přidal do resultu.

Franta: Takže nějak takhle:

    final List result=new ArrayList();

for (final Employee employee : company.getEmployees()) {

if (employee.getAge()>30) result.add(employee.getName());

}

Bohouš: No, konečně pěkný kus
kódu.

Franta: Jenže v Groovy by stačil
jeden řádek.

Bohouš: No co, 3x delší.

Franta: Statisticky to vychází
tak 3x – 4x delší v Javě než v Groovy.

Bohouš: Většinu toho kódu ti
stejně nageneroval editor.

Franta: To u toho Groovy kódu
taky.

Bohouš: Kdes na to vzal editor?

Franta: Normálně stáhni plugin
do Eclipse, IDEy nebo NetBeans a můžeš začít.

Bohouš: Počkej, ještě jsem nic
neslíbil.

Franta: Dobře, tak budu
pokračovat v masáži.

Masáž kódem

Bohouš: Skončili jsme u toho, že
editor mi stejně nageneruje většinu toho balastu
kolem, takže mě tolik netrápí, že je Groovy kód
kompaktnější 3x.

Franta: … až 4x.

Bohouš: Klidně 10x, to pro mě
není argument.

Franta: Ale měl by být. Kód
napíšeš jednou, ale potom ho stokrát znovu čteš,
hledáš v něm chyby a ladíš. Nebo třeba někdo jiný to
musí časem pochopit a z různých důvodů upravit. Kód
se píše pro lidi.

Bohouš: Oukej.

Franta: Koukni na tohle:

Jednoduše zapsaný cyklus

    10.times{print 'Huh'}

Násobení stringů pomocí standardně přetížených
operátorů, vypíše aaabb

    String text='a'*3+'b'*2

Třída String v Groovy umí iterovat přes všechny
znaky a na každý zavolat dodaný kód v podobě closure.
Vypíše daný text převedený do upper-case.

    '''

multi-line text

which will be printed

in upper case

'''.each {print it.toUpperCase()}

Práce s kolekcemi, pro úplnost uvedu, že assert
vyhodnocuje pravdivost podmínek

    ["Joe", "Dave"].each {println it}                                   //vypíše všechny položky pole

assert ["Joe", "Dave"][0] == "Joe" //první položka pole

assert ["Joe", "Dave", "Martin"][1..2] == ["Dave", "Martin"] //docela efektní způsob zápisu výběru části pole

Přidávání do seznamu

    company.employees << new Employee(name:"Bohouš", age:28, status:"junior")

nebo

company.employees += new Employee(name:"Bohouš", age:28, status:"junior")



Bohouš: Hezký, ale už jsem
senior.

Franta: Tak sorry.

Bohouš: A co odebírat ze
seznamu, umíš?

Franta: Jo.

    company.employees -= bohous

Bohouš: Proč do toho
konstruktoru předáváš parametry přes jména, např.
'age:28'? To je nutný?

Franta: Není, klidně to dělej
jako do teď v Javě, ale takhle máš možnost zadat jen
některé parametry a nemusiš kvůli tomu definovat
milióny konstruktorů s různými sadami parametrů, nebo
předávat null, když nechceš některý z parametrů
zadávat.


Teď ti ještě ukážu, jak jsou mapy a kolekce
podporovány přímo na úrovni syntaxe jazyka.

    Map map=["joe":10, "dave":12]

Dva různé způsoby přístupu k položkám v mapě

    assert map["dave"] == 12

assert map.dave == 12

Dva různé způsoby nastavení hodnot položek v mapě

    map.dave = 20

assert map.'dave' == 20



map['dave'] = 30

assert map.'dave' == 30

Groovy přidává nové metody k třídám v JDK, včetně
přetížení operátorů, hlavně ke stringům, číslům,
kolekcím či polím.


Podívej třeba na datumy, tohle v Javě na jeden řádek
nenapíšeš:

    use (TimeCategory) {

println "Tomorrow: ${1.day.from.today}"



println "A week ago: ${1.week.ago}"



println "Date: ${1.month.ago + 1.week + 2.hours - 5.minutes}"

}

Kolekce toho taky umí hodně navíc, třeba jejich
metody any a every stojí za povšimnutí.

    if (company.employees.any {it.status == 'junior'}) offerTrainingTo(company)



if (company.employees.every {it.status == 'senior'}) hireForNextProject(company)

Tomuhle vnořování Groovy do Stringů se říká
GString, uvnitř těch složených závorek můžeš psát
libovolný Groovy kód.

    String employeeDescription="Employee ${employee.name}, ${employee.age} years old"

Takový přehlednější substring, všimni si toho
negativního indexu na konci (vrátí string od nulté
pozice do páté pozice od konce).

    //Vytáhne jméno souboru bez přípony

String fileNameWithoutExtension='tomcat_log_2007_07_10_02x_demo.log'[0..-5]

Regulární výrazy jsou zabudovány přímo do Groovy…

    if ('abcabcabc' ==~ /(abc)+/) {

println "Je to tam"

}

… a můžem třeba hned kouknout, jestli se někde na
webu nepíše o Groovy nebo Ruby:

    ['http://www.javalobby.org', 'http://www.theserverside.com', 'http://www.infoq.com'].each {address ->

[/Groovy/, /Ruby/, /Grails/, /Rails/].each {pattern ->

if (new URL(address).text =~ pattern) {

println "O $pattern se píše na $address"

}

}

}

Bohouš: Prima

Buildery

Franta: Taky tě možná zaujme,
jak se v Groovy pracuje s XML. Třeba vytvoření nového
XML dokumentu pomocí Markup Builderu.

    def writer = new StringWriter()

def xml = new MarkupBuilder(writer)



xml.invoice() {

customer() {

name('Ferda')

address(type:'Delivery') {

street('Pod dubem')

number('10')

}

address(type:'Billing') {

street('Za dubem')

number('20')

}

}

product(id:'00001') {

quantity(50)

}

}

println writer.toString()

A jak bude vypadat výsledný XML dokument…?

Bohouš: No, myslím, že je to
zřejmé.

Franta: Buildery jsou užitečné
na práci se stromovými datovými strukturami, třeba v
podobě XML dat, HTML dokumentů, nebo třeba GUI
komponent. Díky tomu potom struktura kódu pracujícího
s hierarchickou datovou strukturou odpovídá vizuálně
i logicky té manipulované struktuře. To samozřejmě
vede k menšímu počtu chyb při psaní a snazšímu
pochopení a úpravám kódu později. Několik takových
builderů budeme potřebovat nadefinovat v tom našem
projektu pro naše vlastní data.

Closures

Bohouš: Ještě něco??

Franta: Třeba properties – getry
a setry pro property se ti vytvoří až za runtime,
pokud je nepotřebuješ upravit vlastním kódem.

Bohouš: To bude v Javě 7 asi
taky.

Franta: Je na čase. Taky by v ní
měly přibýt closures.

Bohouš: To už v Groovy je, že?

Franta: No jasně, s nimi se to
teprve pěkně rozjíždí.

    def triple = {x -> x * 3}              //definice closury ztrojnásobující svůj jediný parametr

{[1, 2, 3].collect {triple(it)} //vrátí nový seznam se všemi hodnotami ztrojnásobenými

Closury můžeš ukládat do proměnných nebo fieldů, a tak
změnou closury za běhu změnit chování objektů. Nebo
můžeš fixovat hodnotu některých parametrů closury
pomocí metody curry() a vytvořit tak novou closuru s
upraveným chováním.

    def multiply = {x, y -> x * y}         //obecné násobení dvou čísel

assert 6 == multiply(2, 3)

def triple = multiply.curry(3) //definice nové closury ztrojnásobující svůj jediný parametr, něco jako {3, y -> 3 * y}

[1, 2, 3].collect() {triple(it)} //vrátí nový seznam se všemi hodnotami ztrojnásobenými

Bohouš: Paráda.

Franta: To tedy jo.

Shrnutí

Bohouš: Takže z toho, co's
řekl, vyplývá, že Groovy:

  • integruje do Javy, včetně EE,
  • umožňuje využívat všechny existující Java
    knihovny, včetně JDK samotného,
  • obaluje standardní třídy z JDK, zvláště čísla,
    datumy, stringy a kolekce novou funkcionalitou,
  • rozšiřuje syntaxi Javy o podporu properties,
    closures, named parametry a snadnou práci se
    seznamy a mapami,
  • regulární výrazy jsou standardní součástí
    jazyka,
  • GString dovoluje vkládat Groovy kód do stringů,
    což umožňuje definovat vlastně takové jednoduché
    šablony,
  • pro hierarchické datové struktury Groovy nabízí
    koncept builderů, s před-připravenými buildery pro
    XML, HTML, Swing a stromy,
  • nabízí GPath pro snadné traversování mezi
    objeky, včetně kolekcí,
  • zahrnuje ranges (např. 2..5)
    jako samostatný datový typ.

Franta: Přesně. Ještě jsme se
nepustili do několika pokročilejších věcí, třeba:

  • vytváření custom builderů pro vlastní
    hierarchická data,
  • integrovaná práce s textovými šablonami,
  • groovlets pro rychlý vývoj webovských aplikací,
  • práce s relačními databázemi,
  • meta-programming – dynamické vyvolávání metod a
    možnost definovat metodu či celou třídu za běhu,
  • přidávání metod k existujícím knihovním Java a
    Groovy třídám,
  • vytváření interních DSL (Domain Specific
    Language),
  • a ještě pár dalších věcí, na které jsem
    zapomněl.

Pokud budeš mít chuť, můžeš mrknout na tyhle linky:

Franta: Tak co, je Groovy žúžo?


Poznámka: Groovy neboli
zaběhnutý, zaběhaný, rutinní, prima, bezva, fajn,
žúžo.