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.