30 czerwca 2009

Z rozdziału 9. "Working with XML" z "Programming Groovy"

2 komentarzy
W rozdziale 9. "Working with XML" w "Programming Groovy: Dynamic Productivity for the Java Developer" autor przedstawia różne techniki obsługi plików XML w Groovy. Każdorazowo przy nowym temacie Venkat wprowadza nas w temat ciekawym stwierdzeniem. Jako, że ja należę do tego grona osób, które zachłysnęły się prostotą Groovy, więc stanowią one dla mnie dodatkową pożywkę za stosowaniem Groovy w miejscach, w których wielu nawet Javy miałoby obawy zastosować - chociażby obsługa XMLi. Groovy nie będzie pełnym antidotum na XMLowe bolączki, ale biorąc znaczne uproszczenia jakie wprowadza spotkanie z XMLem może być zauważalnie przyjemniejsze.

"Working with traditional Java APIs and libraries to create and parse XML documents tends to lower my spirits. And naigating the document hierarchy using the DOM API is one sure way to drive me insane."

Można się z tym zgodzić bądź nie, ale pochylić się nad tym i rozważyć rozwiązania alternatywne zdecydowanie warto. Może się w końcu okazać, że będziemy mieli więcej czasu na inne czynności niż obsługa XMLi.

Na początek omawiana jest klasa DOMCategory, która korzysta z DOM do obsługi XML. Klasa korzysta z mechanizmu kategorii w Groovy, które pozwalają definiować nowe metody w klasach bez ich modyfikacji (!) DOMCategory dodaje metody tak, że nawigacja po strukturze DOM sprowadza się do odczytu atrybutów klasy (która de facto tych atrybutów nie ma, ale właśnie z DOMCategory mamy wrażenie, jakby miała). Atrybuty dostępne są za pomocą konstrukcji @<nazwa-atrybutu>. Podobnie jak XPath służy do nawigacji po XML, tak GPath umożliwia nawigację po hierarchii POJO i POGO oraz XML. Przykład powinien rozwiać wszelkie wątpliwości.
 groovy:000> f = File.createTempFile("dane", ".xml")
===> c:\temp\dane7020472904153694958.xml
groovy:000> tresc = """<serwery>
groovy:001> <serwer nazwa="Apache Geronimo">
groovy:002> <dostawca>Apache Software Foundation</dostawca>
groovy:003> </serwer>
groovy:004> <serwer nazwa="JBoss Application Server">
groovy:005> <dostawca>JBoss.org</dostawca>
groovy:006> </serwer>
groovy:007> </serwery>"""
===> <serwery>
<serwer nazwa="Apache Geronimo">
<dostawca>Apache Software Foundation</dostawca>
</serwer>
<serwer nazwa="JBoss Application Server">
<dostawca>JBoss.org</dostawca>
</serwer>
</serwery>
groovy:000> f << tresc
===> c:\temp\dane7020472904153694958.xml
groovy:000> f.text
===> <serwery>
<serwer nazwa="Apache Geronimo">
<dostawca>Apache Software Foundation</dostawca>
</serwer>
<serwer nazwa="JBoss Application Server">
<dostawca>JBoss.org</dostawca>
</serwer>
</serwery>
groovy:000> doc = groovy.xml.DOMBuilder.parse(new FileReader(f))
===> [#document: null]
groovy:000> rootElement = doc.documentElement
===> [serwery: null]
groovy:000> use(groovy.xml.dom.DOMCategory) {
groovy:001> println "Serwery i ich dostawcy"
groovy:002> serwery = rootElement.serwer
groovy:003> serwery.each { serwer -> println "${serwer.'@nazwa'} dostarczany jest przez ${serwer.dostawca[0].text()}" }
groovy:004> }
Serwery i ich dostawcy
Apache Geronimo dostarczany jest przez Apache Software Foundation
JBoss Application Server dostarczany jest przez JBoss.org
===> groovy.xml.dom.DOMCategory$NodesHolder@6a2f81
W ramach use korzystałem z atrybutów serwer, dostawca i @nazwa, jakby były one zdefiniowane w ramach odpowiednich typów. Wszystko za sprawą konstrukcji use (więcej o tym w kolejnym wpisie, niebawem).

Kolejnym podejściem do obsługi plików XML jest wykorzystanie groovy.util.XMLParser. Podobnie działa jak DOMCategory, aczkolwiek nie nakłada obowiązku użycia bloku use.
 groovy:000> f = File.createTempFile("dane", ".xml")
===> c:\temp\dane483287696987389412.xml
groovy:000> tresc = """<serwery>
groovy:001> <serwer nazwa="Apache Geronimo">
groovy:002> <dostawca>Apache Software Foundation</dostawca>
groovy:003> </serwer>
groovy:004> <serwer nazwa="JBoss Application Server">
groovy:005> <dostawca>JBoss.org</dostawca>
groovy:006> </serwer>
groovy:007> </serwery>"""
===> <serwery>
<serwer nazwa="Apache Geronimo">
<dostawca>Apache Software Foundation</dostawca>
</serwer>
<serwer nazwa="JBoss Application Server">
<dostawca>JBoss.org</dostawca>
</serwer>
</serwery>
groovy:000> f << tresc
===> c:\temp\dane483287696987389412.xml
groovy:000> doc = new XmlParser().parse(f)
===> serwery[attributes={}; value=[serwer[attributes={nazwa=Apache Geronimo}; value=[dostawca[attributes={};
value=[Apache Software Foundation]]]], serwer[attributes={nazwa=JBoss Application Server};
value=[dostawca[attributes={}; value=[JBoss.org]]]]]]
groovy:000> println "Serwery i ich dostawcy"
Serwery i ich dostawcy
===> null
groovy:000> doc.each {
groovy:001> println "${it.@nazwa} dostarczany jest przez ${it.dostawca[0].text()}"
groovy:002> }
Apache Geronimo dostarczany jest przez Apache Software Foundation
JBoss Application Server dostarczany jest przez JBoss.org
===> serwery[attributes={}; value=[serwer[attributes={nazwa=Apache Geronimo}; value=[dostawca[attributes={};
value=[Apache Software Foundation]]]], serwer[attributes={nazwa=JBoss Application Server};
value=[dostawca[attributes={}; value=[JBoss.org]]]]]]
Brak użycia bloku use oraz kilka atrakcji w stylu each(), collect(), czy find() dla elementów xmlowych stanowi ciekawą alternatywę dla DOMCategory. W przypadku tego rozwiązania komentarze oraz instrukcje XML nie są zachowane.

Użycie XmlParser wiąże się z dużym narzutem pamięciowym, więc kolejnym rozwiązaniem, znacznie mniej pamięciożernym, jest XmlSlurper. Użycie podobne do XmlParser z tym, że dodatkowo mamy obsługę przestrzeni nazewniczych w XML przez określenie ich przez metodę declareNamespace().
 f = File.createTempFile("dane", ".xml")
f << tresc
tresc = """<p:serwery xmlns:p="Przyklad">
<p:serwer nazwa="Apache Geronimo">
<p:dostawca>Apache Software Foundation</p:dostawca>
</p:serwer>
</p:serwery>"""
doc = new XmlSlurper().parse(f).declareNamespace(pk: 'Przyklad')
println "${doc.'pk:serwer'.@nazwa} dostarczany przez ${doc.'pk:serwer'.dostawca[0].text()}"
Tworzenie XMLi już widzieliśmy. Po prostu korzystamy z potrójnego cudzysłowu do utworzenia treści XML, w której rozwiązywane są wyrażenia Groovy.
 groovy:000> serwery = ['Apache Geronimo':'Apache Software Foundation', 'JBoss AS':'JBoss.org']
===> {Apache Geronimo=Apache Software Foundation, JBoss AS=JBoss.org}
groovy:000> tresc = ''
===>
groovy:000> serwery.each { serwer, dostawca ->
groovy:001> wycinek = """
groovy:002> <serwer nazwa="${serwer}">
groovy:003> <dostawca>${dostawca}</dostawca>
groovy:004> </serwer>
groovy:005> """
groovy:006>
groovy:006> tresc += wycinek
groovy:007> }
===> {Apache Geronimo=Apache Software Foundation, JBoss AS=JBoss.org}
groovy:000> doc = "<serwery>${tresc}</serwery>"
===> <serwery>
<serwer nazwa="Apache Geronimo">
<dostawca>Apache Software Foundation</dostawca>
</serwer>

<serwer nazwa="JBoss AS">
<dostawca>JBoss.org</dostawca>
</serwer>
</serwery>
groovy:000>
groovy:000> println doc
<serwery>
<serwer nazwa="Apache Geronimo">
<dostawca>Apache Software Foundation</dostawca>
</serwer>

<serwer nazwa="JBoss AS">
<dostawca>JBoss.org</dostawca>
</serwer>
</serwery>
===> null
Można również skorzystać z MarkupBuilder lub StreamingMarkupBuilder. Podsumowując: "Groovy can make working with XML bearable" (str. 155). Czyż nie?

29 czerwca 2009

grails upgrade i morph-deploy, finalny NetBeans 6.7 oraz grailsowy Refcard

1 komentarzy
Jakiś czas temu zainstalowałem nową wersję Grails 1.1.1. W trakcie aktualizacji kasuję wszystkie katalogi związane z poprzednią wersją oraz te, w katalogu domowym (%userprofile%/.grails). W międzyczasie dostałem informację o braku aktywności na Morph i zamknięciu usługi, jeśli w ciągu nadchodzących 7 dni nie wykonam aktualizacji, więc naturalnie pierwszą wtyczką do instalacji stała się morph-deploy. Okazało się, że w trakcie instalacji pojawił się komunikat o konieczności uaktualnienia Grails w samej aplikacji.
 jlaskowski@work /cygdrive/c/projs/sandbox/nauczyciel
$ grails install-plugin morph-deploy
Welcome to Grails 1.1.1 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: c:/apps/grails

Base Directory: C:\projs\sandbox\nauczyciel
Running script c:\apps\grails\scripts\InstallPlugin.groovy
Environment set to development
Application expects grails version [1.1], but GRAILS_HOME is version [1.1.1] -
use the correct Grails version or run 'grails upgrade'
if this Grails version is newer than the version your application expects.
Jeszcze nigdy wcześniej nie spotkałem się z tym komunikatem. Wykonanie zalecanego grails upgrade zajęło tylko chwilę.
 jlaskowski@work /cygdrive/c/projs/sandbox/nauczyciel
$ grails upgrade
Welcome to Grails 1.1.1 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: c:/apps/grails

Base Directory: C:\projs\sandbox\nauczyciel
Running script c:\apps\grails\scripts\Upgrade.groovy
Environment set to development
NOTE: Your application currently expects grails version [1.1], this target will upgrade it to Grails 1.1.1 ...

WARNING: This target will upgrade an older Grails application to 1.1.1.
However, tag libraries provided by earlier versions of Grails found in grails-app/taglib will be removed.
The target will not, however, delete tag libraries developed by yourself.
Are you sure you want to continue?
(y, n)
y
...
Please make sure you view the README for important information about changes to your source code. ...
Project upgraded
Po tym instalacja wtyczki przebiegła już całkiem gładko.
 jlaskowski@work /cygdrive/c/projs/sandbox/nauczyciel
$ grails install-plugin morph-deploy
Welcome to Grails 1.1.1 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: c:/apps/grails

Base Directory: C:\projs\sandbox\nauczyciel
Running script c:\apps\grails\scripts\InstallPlugin.groovy
Environment set to development
Reading remote plugin list ...
[get] Getting: http://svn.codehaus.org/grails/trunk/grails-plugins/.plugin-meta/plugins-list.xml
[get] To: C:\Documents and Settings\jlaskowski\.grails\1.1.1\plugins-list-core.xml
.
[get] last modified = Tue Jun 09 13:03:55 CEST 2009
Reading remote plugin list ...
[get] Getting: http://plugins.grails.org/.plugin-meta/plugins-list.xml
[get] To: C:\Documents and Settings\jlaskowski\.grails\1.1.1\plugins-list-default.xml
.........................
[get] last modified = Sat Jun 13 05:45:37 CEST 2009
[get] Getting: http://plugins.grails.org/grails-morph-deploy/tags/RELEASE_0_1/grails-morph-deploy-0.1.zip
[get] To: C:\Documents and Settings\jlaskowski\.grails\1.1.1\plugins\grails-morph-deploy-0.1.zip
.....
[get] last modified = Thu Jan 22 00:21:40 CET 2009
[copy] Copying 1 file to C:\Documents and Settings\jlaskowski\.grails\1.1.1\projects\nauczyciel\plugins
Installing plug-in morph-deploy-0.1
[mkdir] Created dir: C:\Documents and Settings\jlaskowski\.grails\1.1.1\projects\nauczyciel\plugins\morph-deploy-0.1
[unzip] Expanding: C:\Documents and Settings\jlaskowski\.grails\1.1.1\plugins\grails-morph-deploy-0.1.zip into
C:\Documents and Settings\jlaskowski\.grails\1.1.1\projects\nauczyciel\plugins\morph-deploy-0.1
Executing morph-deploy-0.1 plugin post-install script ...
Plugin morph-deploy-0.1 installed
Plug-in provides the following new scripts:
------------------------------------------
grails deploy
Found events script in plugin morph-deploy
Tyle tylko, że samo uruchomienie wtyczki przypomniało mi o błędzie, który sądziłem, że został poprawiony dawno temu. Czy tylko ja korzystam z tej wtyczki?! (na pewno ja nie zgłosiłem tego błędu, więc to może być przyczyna problemu :))
 jlaskowski@work /cygdrive/c/projs/sandbox/nauczyciel
$ grails war
Welcome to Grails 1.1.1 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: c:/apps/grails

Base Directory: C:\projs\sandbox\nauczyciel
Running script c:\apps\grails\scripts\War.groovy
Environment set to production
Warning, target causing name overwriting of name startLogging
Removing JDBC 2 Extensions JAR from WAR for Mor.ph deployment
Error executing script War: No such property: stagingDir for class: _Events
gant.TargetMissingPropertyException: No such property: stagingDir for class: _Events
at gant.Gant$_dispatch_closure4.doCall(Gant.groovy:329)
at gant.Gant$_dispatch_closure6.doCall(Gant.groovy:334)
at gant.Gant$_dispatch_closure6.doCall(Gant.groovy)
at gant.Gant.withBuildListeners(Gant.groovy:344)
at gant.Gant.this$2$withBuildListeners(Gant.groovy)
at gant.Gant$this$2$withBuildListeners.callCurrent(Unknown Source)
at gant.Gant.dispatch(Gant.groovy:334)
at gant.Gant.this$2$dispatch(Gant.groovy)
at gant.Gant.invokeMethod(Gant.groovy)
at gant.Gant.processTargets(Gant.groovy:495)
at gant.Gant.processTargets(Gant.groovy:480)
Caused by: groovy.lang.MissingPropertyException: No such property: stagingDir for class: _Events
at _Events$_run_closure1.doCall(_Events.groovy:6)
at War$_run_closure1.doCall(War.groovy:38)
at gant.Gant$_dispatch_closure4.doCall(Gant.groovy:324)
... 10 more
Wystarczy skasować skrypt _Events.groovy z katalogu wtyczki morph-deploy...
 jlaskowski@work /cygdrive/c/projs/sandbox/nauczyciel
$ rm -rf c\:/Documents\ and\ Settings/jlaskowski/.grails/1.1.1/projects/nauczyciel/plugins/morph-deploy-0.1/scripts/_Events.groovy
i ponownie uruchomić grails war.
 jlaskowski@work /cygdrive/c/projs/sandbox/nauczyciel
$ grails war
Welcome to Grails 1.1.1 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: c:/apps/grails

Base Directory: C:\projs\sandbox\nauczyciel
Running script c:\apps\grails\scripts\War.groovy
Environment set to production
Warning, target causing name overwriting of name startLogging
...
Done creating WAR C:\projs\sandbox\nauczyciel/nauczyciel-0.1.1.war
A później już tylko grails deploy, username/password do Morpha i można cieszyć się wdrożoną aplikacją grailsową - niewielką, ale własną!
 jlaskowski@work /cygdrive/c/projs/sandbox/nauczyciel
$ grails deploy
Welcome to Grails 1.1.1 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: c:/apps/grails

Base Directory: C:\projs\sandbox\nauczyciel
Running script C:\Documents and Settings\jlaskowski\.grails\1.1.1\projects\nauczyciel\plugins\morph-deploy-0.1\scripts\Deploy.groovy
Environment set to development
Warning, target causing name overwriting of name default
This will deploy the file nauczyciel-0.1.1.war
Enter your Mor.ph username:
*******
Enter your Mor.ph password:
*******
Please wait...
[java] Uploading the code...
[java] Creating new appspace version...
[java] Deploying the application...
[java] Deploy Done.
[java] For more information on the status of this deployment, you
[java] can view the Deployment Logs by clicking 'Manage' located
[java] on your subscription widget and by clicking the Logs tab.
[java] In this same page, you can also view your Production logs
[java] and Scheduled task logs.
[java] ** transaction commit **
Tym razem wszystko poszło już gładko. Nowa wersja aplikacji jest na Morphie. Pomysły, co mogłaby oferować mile widziane. Zdecydowanie za rzadko do niej zaglądam, więc mogłoby się to zmienić po kilku wskazówkach ;-)

p.s. Pojawiła się finalna wersja NetBeans IDE 6.7. Jedną z oczekiwanych funkcjonalności było wsparcie dla Grails 1.1 i to faktycznie otrzymaliśmy. Więcej o zmianach w temacie wsparcia Groovy i Grails na stronie Groovy and Grails.

p.s.II Pojawił się dokument Getting Started with Grails z serii Refcard (pisał o tym również Mateusz w Getting started with Grails refcard, więc pewnie już wiecie). W połączeniu z NetBeans 6.7 nie można już narzekać na brak narzędzi i wsparcia do nauki Grails. Teraz jedynym czynnikiem ograniczającym jest...czas. Ale z tym zawsze jest i będzie problem, jeśli się człowiek zajmuje pierdołami zamiast czymś pożytecznym. Jeszcze nie sprawdziłem zawartości, ale nie omieszkam.

p.s.III (ostatni, ale nie mniej ważny) Chciałbym tym samym nagłośnić pomysł, który chciałbym wdrożyć podczas jesiennej edycji Warsjavy i powrócić z ideą tej konferencji - warsztatami. Niech to będzie połączenie pomysłu uatrakcyjnienia spotkań Warszawa JUG, gdzie promuje się 20-30-minutowe wystąpienia, aby szybko i bez zbędnych wprowadzeń zademonstrować wybrany projekt. Myślę o pewnego rodzaju publicznej selekcji propozycji, gdzie prelegent musiałby postarać się o reklamę swojego przedsięwzięcia i tylko te wchodzą, które zdobędą największą liczbę głosów. Pewnie należałoby spodziewać się jakieś aplikacji, ale skoro jej jeszcze nie ma, a miałbym ją stworzyć, to pewnie jej w ogóle nie będzie :) Jeśli nie, to wybierze kapituła konferencji. Najlepiej byłoby, gdyby udało się uruchomić warsztaty w jakimś pubie z rzutnikiem o dobrej rozdzielczości, powiedzmy przez 4 godziny. To daje około 8 30-minutowych prezentacji z przerwami 15 minutowymi. Mottem przewodnim byłyby języki dynamiczne i ich projekty - Groovy (z Grails), Scala (z Liftem), Clojure (z ???) czy wręcz takie cuda jak F#. Może mogłoby to być wspólne przedsięwzięcie grup javowych z Warszawy i innych miast JUGowych, a także grup MS (ze względu na F#). Dlaczego nie?! Jeśli celem miałoby być łatwe i przyjemne przyswojenie wiedzy z dziedziny dynamicznych języków obiektowych z elementami funkcyjnymi (taki językowy gulasz), to widzę we współpracy jedynie same zalety. Wciąż jednak pomysł przyćmiewany nadchodzącą Javarsovią, na której będzie można przedyskutować temat z Tobą, Tobą i z Tobą również. Będzięcie, prawda? Ja siedzę w Bratysławie, więc pojawię się dopiero pod sam koniec i to pewnie dopiero na...SPOINIE spoić się z Wami ;-)

28 czerwca 2009

Z rozdziału 8. o Groovy SDK z "Programming Groovy"

0 komentarzy
Rozdział 8. "Exploring the GDK" rozpoczyna część drugą zatytułowaną "Using Groovy". Do tej pory autor koncentrował swoją uwagę na składni języka i wszystkim, co było w zasadzie konieczne, aby przykuć uwagę czytelnika i zachęcić go do dalszej lektury (w efekcie poznania Groovy). Należało w końcu wpłynąć na twarde (=statyczne i silnie typowane) myślenie javowe i pokazać urok Groovy. Nie, nie jest i nie będzie to początek moich peanów nt. Groovy i wskazywanie go jako jedynego słusznego języka programowania. Odnoszę jednak wrażenie, że wielu z Was z pewnością poczuło ten dreszczyk emocji poznania nowego języka (Grails raz jeszcze), który oferuje możliwości Javy w połączeniu z elementami języków dynamicznych i funkcyjnych. W końcu warto uczyć się nowych języków, aby nasze myślenie było bardziej elastyczne i posiadało umiejętność dopasowania się do ciągłych zmian w projekcie, w życiu, gdziekolwiek. W końcu i przysłowiowym młotkiem można wiele zdziałać, ale do krojenia chleba znacznie lepiej skorzystać z noża (było też o tym w książce "Pragmatyczny programista - od czeladnika do mistrza", ale Krzysiek o tym nie wspomina - Pragmatyczny programista). Do niedawna sądziłem, że poznawanie nowego języka jest dobre, ale nie było ku temu jakiś wyraźnych powodów, aby pomysł zrealizować. Kiedy jednak się udało i mogę powiedzieć, że choć trochę liznąłem Groovy, widzę, ile radości mi to sprawiło. Największą zaletą Groovy jest możliwość niedalekiego odejścia od JVM. Wciąż z niej korzystam i to daje mi możliwość czerpania z Groovy tylko tyle, ile potrzebuję i w czym czuję się pewniej z jednoczesnym utrzymaniem tych samych narzędzi programistycznych. Owa bariera poznawcza (aka krzywa uczenia) stała się dzięki temu niewielka i wymaga(ła) jedynie poznania kilku konstrukcji językowych. Czyż tego samego nie oczekują od nas szkielety aplikacyjne? Potraktujmy Groovy jako jeden z wielu szkieletów aplikacyjnych i stosujmy właściwie do okoliczności. Nic na siłę.

Początek rozdziału to kolejne obietnice wręcz natychmiastowego zwiększenia produktywności z Groovy, bo nie tylko, że mamy większą dynamikę języka, ale i rozszerzone biblioteki bazowe Javy (Java API). Metody znane z relacji rozdziału o kolekcjach (Z rozdziału 7. o kolekcjach (listach) z "Programming Groovy" oraz Dokończenie rozdziału 7. "Working with Collections" z "Programming Groovy") - each(), collect(), find(), findAll(), any() oraz every() są faktycznie dostępne w każdej klasie javowej. To zdecydowanie zmienia nasze postrzeganie, czego możnaby oczekiwać więcej od java.lang.Object. Niektórych przyprawi to o ból głowy, że i sama Java wymaga wiele nauki, a tu proszę, Groovy jeszcze dodaje swoje trzy grosze. Nie ma lekko. Jeśli pozwolimy sobie na większą elastyczność jest pewnym, że członkowie zespołu projektowego podziękują nam za inspirujące pomysły (wierzę, że poznanie jakiegokolwiek nowego języka to wkroczenia na teren niedostępny innym, którzy nim nie władają - czy to język mówiony, czy programowania).

Nowymi metodami, których autor wcześniej nie przedstawiał, są dump() oraz inspect().
 groovy:000> class Osoba {
groovy:001> String imie, nazwisko
groovy:002> }
===> true
groovy:000> jacek = new Osoba(imie: 'Jacek', nazwisko: 'Laskowski')
===> Osoba@1382926
groovy:000> jacek.dump()
===> <Osoba@1382926 imie=Jacek nazwisko=Laskowski>
groovy:000> jacek.inspect()
===> Osoba@1382926
Za pomocą dump() możemy spojrzeć na strukturę i dane egzemplarza lub też samego typu. Wszystko, co może być przydatne w trakcie śledzenia działania aplikacji, odnotowania w logach (dziennikach zdarzeń) czy po prostu nauki. Metoda inspect() pozwala nam poznać, co jest potrzebne do utworzenia obiektu danego typu. Jak na razie nie zachwyciła mnie (pewnie jeszcze nie zrozumiałem sensu jej istnienia).

Miłośnicy JavaScript czy VBScript będą uradowani dowiadując się, że znana im metoda with() jest również dostępna w Groovy. Służy ona do określenia kontekstu wykonania metod w ramach przekazanego do niej domknięcia (przypomina mi to maćkowe dywagacje w Ograniczony kontekst). Zamiast serii metod na danym egzemplarzu, co w Javie wymagałoby podawania go każdorazowo, w Groovy można krócej. Jej aliasem jest identity() i wywołanie obu zwraca ten sam rezultat. Przykład od razu wyjaśni o co chodzi.
 groovy:000> lista = [1,2]
===> [1, 2]
groovy:000> lista.add(3)
===> true
groovy:000> lista.add(4)
===> true
groovy:000> lista
===> [1, 2, 3, 4]
groovy:000> println lista.size()
4
===> null
groovy:000> println lista.contains(2)
true
===> null
groovy:000> // a teraz prosciej
===> true
groovy:000> nowaLista = [1,2]
===> [1, 2]
groovy:000> nowaLista.with {
groovy:001> add(3)
groovy:002> add(4)
groovy:003>
groovy:003> println size()
groovy:004> println contains(2)
groovy:005> }
4
true
===> null
groovy:000> nowaLista
===> [1, 2, 3, 4]
Zamiast każdorazowego określania egzemplarza, który ma być odbiorcą wywołania serii metod, wystarczy with() z domknięciem. W ramach tego domknięcia wszystkie metody będą kierowane do tego samego obiektu - obiektu, na którym wywołaliśmy metodę with() (lub identity()). Działanie with() polega na właściwym ustawieniu delegate domknięcia.

Metoda sleep() jest udoskonaleniem znanej z Java API metody Thread.sleep(). Ignoruje ona przerwania (wywołanie Thread.interrupt()) przez zadany czas. Parametrem wejściowym sleep() jest domknięcie, które obsługuje przerwanie i które otrzymuje na wejściu InterruptedException. Jeśli domknięcie zwróci false wykonanie sleep() będzie trwało do zadanego czasu, jakby przerwanie nie nastąpiło.
 groovy:000> def prezentacjaSleep(flag) {
groovy:001> thread = Thread.start {
groovy:002> println "Watek rozpoczal sie"
groovy:003> poczatek = System.nanoTime()
groovy:004> new Object().sleep(3000) {
groovy:005> println "Obsluga przerwania z wyjatkiem: " + it
groovy:006> flag
groovy:007> }
groovy:008> koniec = System.nanoTime()
groovy:009> println "Koniec pracy watku - patrz na czasy wykonania - ${(koniec - poczatek)/10**9} sekund"
groovy:010> }
groovy:011> thread.interrupt()
groovy:012> thread.join()
groovy:013> }
===> true
groovy:000> prezentacjaSleep(true)
Watek rozpoczal sie
Obsluga przerwania z wyjatkiem: java.lang.InterruptedException: sleep interrupted
Koniec pracy watku - patrz na czasy wykonania - 0.001169143 sekund
===> null
groovy:000> prezentacjaSleep(false)
Watek rozpoczal sie
Obsluga przerwania z wyjatkiem: java.lang.InterruptedException: sleep interrupted
Koniec pracy watku - patrz na czasy wykonania - 3.00024193 sekund
===> null
Zwróć uwagę na czasy wykonania wątków (oraz nową metodę w klasie Thread start()).

Za pomocą operatora tablicowego [] (obsługiwany przez metodę getAt() lub putAt() w zależności od trybu - odczyt/zapis) możemy dynamicznie dostać się do właściwości obiektu. Zamiast statycznego (w sensie podania go w trakcie pisania tej konstrukcji, a nie kwalifikatora static) wywołania egzemplarz.wlasciwosc, można również dynamicznie - egzemplarz['wlasciwosc']. Zaletą takiego wywołania jest możliwość odczytu/zapisu właściwości nie znając jej nazwy podczas pisania aplikacji, np. po określeniu jej przez użytkownika.
 groovy:000> jacek.dump()
===> <Osoba@1382926 imie=Jacek nazwisko=Laskowski>
groovy:000> jacek.imie
===> Jacek
groovy:000> jacek.nazwisko
===> Laskowski
groovy:000> dostepneWlasciwosci = ['imie', 'nazwisko']
===> [imie, nazwisko]
groovy:000> dostepneWlasciwosci.each { nazwa ->
groovy:001> println "${nazwa} = ${jacek[nazwa]}"
groovy:002> }
imie = Jacek
nazwisko = Laskowski
===> [imie, nazwisko]
groovy:000> jacek[dostepneWlasciwosci[0]] = 'JACEK'
===> JACEK
groovy:000> jacek.dump()
===> <Osoba@1382926 imie=JACEK nazwisko=Laskowski>
Wystarczy teraz użyć kolejnej metody getProperties() (albo prościej properties), aby dostać się do atrybutów danego typu i wykonać na nich pewne czynności.
 groovy:000> jacek.properties.each { prop -> println "$prop" }
class=class Osoba
imie=JACEK
nazwisko=Laskowski
metaClass=org.codehaus.groovy.runtime.HandleMetaClass@cf3539[groovy.lang.MetaClassImpl@cf3539[class Osoba]]
===> {class=class Osoba, imie=JACEK, nazwisko=Laskowski,
metaClass=org.codehaus.groovy.runtime.HandleMetaClass@cf3539[groovy.lang.MetaClassImpl@cf3539[class Osoba]]}
Oczywiście w samej Javie też to jest możliwe z wykorzystaniem Reflection API, ale prostota takiego podejścia jest nie do przecenienia. Do tego należy dodać, że wykonanie metod dynamicznie jest równie proste z invokeMethod() (autor dosyć sarkastycznie wprowadza w temat i szkoda byłoby zepsuć Wam poznanie tego osobiście - zapraszam na stronę 139. książki).
 groovy:000> jacek.metaClass.pewnaMetodaZParametrami { String p1, int p2 -> println "Podano p1=$p1 i p2=$p2" }
===> null
groovy:000> jacek.invokeMethod("pewnaMetodaZParametrami", ["Pierwszy", 2] as Object[])
Podano p1=Pierwszy i p2=2
===> null
Wszystkie wymienione metody są dostępne dla java.lang.Object, a tym samym i dla wszystkich typów.

W klasie Process dodano trzy atrybuty - in, out oraz err. Dają one dostęp do, odpowiednio, strumieni wejściowych, wyjściowych i błędów. Mamy również atrybut text, który zwraca całe wyjście z procesu. Odczyt standardowego strumienia błędów to err.text.
 groovy:000> proces = "uname -a".execute()
===> java.lang.ProcessImpl@f9104a
groovy:000> proces.in.text
===> CYGWIN_NT-5.1 work 1.5.25(0.156/4/2) 2008-06-12 19:34 i686 Cygwin
Możemy to samo otrzymać z proces.text. Pamiętajmy, że po wykonaniu polecenia proces kończy się i strumień zostaje zamknięty, więc ponowny odczyt kończy się zgłoszeniem wyjątku.
 groovy:000> proces = "uname -a".execute()
===> java.lang.ProcessImpl@486cdd
groovy:000> proces.text
===> CYGWIN_NT-5.1 work 1.5.25(0.156/4/2) 2008-06-12 19:34 i686 Cygwin

groovy:000> proces.text
ERROR java.io.IOException: Stream closed
at groovysh_evaluate.run (groovysh_evaluate:2)
...
Komunikacja z procesem możliwa jest z operatorem lewego przesunięcia <<, np.
 groovy:000> p = "wc".execute()
===> java.lang.ProcessImpl@956254
groovy:000> p.out.withWriter {
groovy:001> it << "wc zlicza liczbe liter, slow i zdan\n"
groovy:002> it << "wiec powinnismy dostac 2 zdania i in.\n"
groovy:003> }
===> java.io.OutputStreamWriter@4d93e3
groovy:000> println p.text
2 14 74

===> null
Wykonanie poleceń z parametrami to wykonanie execute() na liście napisów, gdzie pierwszy z nich jest poleceniem, a reszta parametrami.
 groovy:000> command = ['c:/apps/groovy/bin/groovy.bat', '--version']
===> [c:/apps/groovy/bin/groovy.bat, --version]
groovy:000> command.execute().text
===> Groovy Version: 1.6.3 JVM: 1.6.0_14
Dobre, co?

W Thread widzieliśmy już dodatek w postaci metody start(), która akceptuje domknięcie - zbiór metod uruchamianych w osobnym wątku. Jeśli chcemy stworzyć wątek-demona korzystamy z Thread.startDeamon() z domknięciem na wejściu. Tutaj autor przeszedł samego siebie z wyjaśnieniem różnicy między wątkiem normalnym a demonem (patrz przypis na stronie 142):

"A deamon thread quits if there are no active nondeamon threads currently running - kind of like employees who work only when the boss is around"

A Tyś demonem? :)

W pakiecie java.io również kilka rozszerzeń Groovy. W klasie File mamy eachFile() oraz eachDir() z ich wariacjami, które akceptują domknięcia do nawigacji w systemie plików. Odczyt zawartości pliku, dowolnego obiektu typu Reader czy InputStream to po prostu odczyt atrybutu text.
 groovy:000> f = File.createTempFile("temp", ".txt")
===> c:\temp\temp1698516868251566991.txt
groovy:000> f << "jakis tekst\n"
===> c:\temp\temp1698516868251566991.txt
groovy:000> f << 'i jeszcze inny\n'
===> c:\temp\temp1698516868251566991.txt
groovy:000> f << 'Jeszcze kolejna linia\n'
===> c:\temp\temp1698516868251566991.txt
groovy:000> f.text
===> jakis tekst
i jeszcze inny
Jeszcze kolejna linia

groovy:000> f.eachLine { linia -> println "$linia" }
jakis tekst
i jeszcze inny
Jeszcze kolejna linia
===> null
groovy:000> println f.filterLine { it =~ /olejna/ }
Jeszcze kolejna linia

===> null
Automatyczna obsługa otwarcia i zamknięcia pliku możliwa jest metodą withStream(), której parametrem wejściowym jest domknięcie wykonywane z parametrem - obiektem InputStream. Podobnie jest z zapisem z withWriter().

Kolekcje List, Set, SortedMap oraz SortedSet otrzymały metody asImmutable() do ich zmiany na obiekt niezmienny oraz asSynchronized() do utworzenia obiektu "wielowątkowego" (ang. thread-safe).
 groovy:000> l = ['ala', 'ma', 'kota']
===> [ala, ma, kota]
groovy:000> l.class.name
===> java.util.ArrayList
groovy:000> l << 'nowy elment'
===> [ala, ma, kota, nowy elment]
groovy:000> L = l.asImmutable()
===> [ala, ma, kota, nowy elment]
groovy:000> L << "nie mozna dodawac nowych elementow!"
ERROR java.lang.UnsupportedOperationException: null
at groovysh_evaluate.run (groovysh_evaluate:2)
...
groovy:000> l << 'A do l wciaz mozna'
===> [ala, ma, kota, nowy elment, A do l wciaz mozna]
Więcej o tych i innych zmianach na stronie Groovy JDK API Specification.

26 czerwca 2009

Eclipse Galileo i [Poland] Code-House (http) oraz końcówka rejestracji na Javarsovię 2009

0 komentarzy
A to ci dopiero nowina! Tę firmę znam bardziej niż jakąkolwiek inną i jest mirrorem eclipse'owym. Po prostu, coraz więcej Łukasza w Sieci! :)

Po pojawieniu się informacji o nowej wersji Eclipse 3.5 (Galileo) na forum grupy Warszawa JUG od Bartka Zdanowskiego (patrz Galileo przyjechały...) postanowiłem uaktualnić swoją wersję. Jednakże tym razem nie była to czysta chęć posiadania aktualnej wersji, ale informacja, jaką podał Bartek:

W narzędziach zdalnych, dla przykładu, ma połączenie z shellem i można wszystko pięknie monitorować jakie procesy, dostęp do plików, no i zwykły terminal. Strasznie mi się to podoba, bo mamy mnóstwo maszyn na linuxach i sporo się tam dzieje.

Zaintrygował mnie tym. Wciąż nie wiem, o co chodzi dokładniej (postanowiłem to zbadać, ale nie dzisiaj), ale nową wersję już mam. Kiedy przyszło do wybrania mirrora eclipse'owego pojawiła się firma Code-House! A to ci dopiero, pomyślałem. Świetna reklama i jaka tania. Wprost nie mogłem opanować się, aby tego nie skomentować. Czy są jeszcze jakieś lokalne firmy, które zechciały wesprzeć fundację i tym sposobem pokazać, kto tak na prawdę stoi za rozwojem technologii (nawet, jeśli miałoby to być jedynie udostępnienie dysków)?

Czy jest już szczęśliwiec, który rozpoznał temat nowości w Eclipse Galileo? Przygotowujemy się w ramach Warszawa JUG do 15-20-minutówek, których celem miałoby być wprowadzenie świeżości w często sztucznie rozwlekanych tematach i poprowadzenie 2-3 wystąpień w ramach pojedynczego spotkania, a właśnie nowości Galileo mogłyby być świetnym tematem na takie przedsięwzięcie. Chętni? Jeśli tak, to doświadczenie można zebrać już podczas nadchodzącej konferencji Javarsovia 2009 w dniu 4. lipca 2009 w Warszawie. Jeśli jeszcze nie wpisałeś/-aś się na listę uczestników koniecznie to zrób, bo termin wygasa z nadchodzącą niedzielą, 28. czerwca o 23:59. Innymi słowy: do niedzieli jest otwarta rejestracja. Później, hmmm, wstęp jest wolny, ale...tych Pań/Panów nie obsługujemy. Sam wybieraj, w jakiej roli występujesz. Gorąco zachęcam do rejestracji. Dzięki niej możemy znacznie efektywnie (i efektownie) przygotować się logistycznie. A w kontekście tego zbierania doświadczeń, to lista prelegentów jest już zamknięta, ale sam udział i podejrzenie jak inni to robią może znacznie pomóc w późniejszych własnych doświadczeniach prezenterskich. Nic nie zastąpi udziału bezpośredniego w konferencji i zaczerpnięcia pomysłów od innych, aby później prezentować swóje tematy na temat i profesjonalnie(j). Wciąż sę wahasz? Może lista tematów pomoże w podjęciu decyzji?

23 czerwca 2009

Dokończenie rozdziału 7. "Working with Collections" z "Programming Groovy"

0 komentarzy
Kontynuacja relacji z lektury "Programming Groovy: Dynamic Productivity for the Java Developer" Venkata Subramaniama. Tym razem dokończenie rozdziału 7. "Working with Collections" o kolekcjach.

Tworzenie mapy w Groovy przypomina tworzenie listy (patrz Z rozdziału 7. o kolekcjach (listach) z "Programming Groovy") z tym, że elementami są pary - klucz i jego wartość, z dwukropkiem (:) jako separatorem.
 $ groovysh
Groovy Shell (1.6.3, JVM: 1.6.0_14)
Type 'help' or '\h' for help.
-------------------------------------------------------------
groovy:000> mapa = ['klucz1':'wartosc1', 'klucz2':'wartosc2']
===> {klucz1=wartosc1, klucz2=wartosc2}
Dostęp do wartości klucza jest możliwy na dwa sposoby - przez konstrukcję [klucz] lub bezpośrednio przez kropkę z określeniem nazwy klucza.
 groovy:000> mapa['klucz1']
===> wartosc1
groovy:000> mapa.'klucz1'
===> wartosc1
groovy:000> mapa.klucz1
===> wartosc1
Konstrukcja odczytu tablicowego (z []) nie pozwala na określenie nazwy klucza bez pojedynczych cudzysłowów.
 groovy:000> mapa[klucz1]
ERROR groovy.lang.MissingPropertyException: No such property: klucz1 for class: groovysh_evaluate
at groovysh_evaluate.run (groovysh_evaluate:2)
...
W takim przypadku klucz1 traktowane jest jako zmienna.

Jeśli nazwa klucza zawiera zastrzeżone znaki, np. symbole matematyczne, czy spacje, konieczne jest "opakowanie" klucza pojedynczymi cudzysłowami.
 groovy:000> mapa.'klucz ze spacja' = 'pewna wartosc'
===> pewna wartosc
groovy:000> mapa
===> {klucz1=wartosc1, klucz2=wartosc2, klucz ze spacja=pewna wartosc}
groovy:000> mapa.'klucz ze spacja'
===> pewna wartosc
Sprawne oko mogło zauważyć mechanizm dodawania elementów do mapy przez mechanizm "z kropką". Podobnie jak w odczycie, pozbywamy się specjalności niektórych znaków (spacje, symbole matematyczne) przez umieszczenie klucza w pojedynczych cudzysłowach.

Podstawowym wyróżnikiem mapy jako typu w Groovy jest specjalne traktowanie konstrukcji mapa.class. We wszystkich typach w Groovy poza mapą na wyjściu mamy obiekt klasy/typu. W przypadku mapy jest to najzwyklejsza nazwa klucza, więc jeśli go nie ma zwracany jest null.
 groovy:000> mapa.class
===> null
groovy:000> mapa.class = 'Pewna wartosc dla klucza class'
===> Pewna wartosc dla klucza class
groovy:000> mapa.class
===> Pewna wartosc dla klucza class
Wyobraźmy sobie przykładową mapę, w której umieszczamy pary nazwa języka i jego autor. W książce pojawia się mapa z C++, Java i Lisp (pewnie dla dwóch pierwszych znamy autorów, ale dla Lisp już nie, co?).
 groovy:000> jezyki = ['C++':'Stroustrup', 'Java':'Gosling', 'Lisp':'McCarthy']
===> {C++=Stroustrup, Java=Gosling, Lisp=McCarthy}
groovy:000> jezyki.C++
ERROR java.lang.NullPointerException: Cannot invoke method next() on null object
at groovysh_evaluate.run (groovysh_evaluate:2)
...
Próba odczytania wartości dla C++ kończy się NPE (NullPointerException). Już wiemy dlaczego (znaki specjalnego traktowania w Groovy - ++ odpowiadające metodzie next()), ale autor wyjaśnia to w dosyć "interesujący" sposób: "You may discard this example code by saying C++ is always a problem, no matter where you go." (str. 125).

Metoda each() na mapie akceptuje domknięcie, w którym parametrem wejściowym może być pojedynczy element-para mapy (typ MapEntry), lub dwa parametry, które odpowiadają aktualnie przetwarzanemu kluczowi i jego wartości.
 groovy:000> jezyki.each { paraJezykAutor -> println "Jezyk: $paraJezykAutor.key, autor: $paraJezykAutor.value" }
Jezyk: C++, autor: Stroustrup
Jezyk: Java, autor: Gosling
Jezyk: Lisp, autor: McCarthy
===> {C++=Stroustrup, Java=Gosling, Lisp=McCarthy}
groovy:000> jezyki.each { jezyk, autor -> println "Jezyk: $jezyk, autor: $autor" }
Jezyk: C++, autor: Stroustrup
Jezyk: Java, autor: Gosling
Jezyk: Lisp, autor: McCarthy
===> {C++=Stroustrup, Java=Gosling, Lisp=McCarthy}
Ogólnie, wszystkie metody na mapie akceptują pojedynczy parametr wejściowy, dla których przekazywana jest para, a dla dwóch klucz i jego wartość. Tak będzie dla collect(), find(), findAll().

Nowością, w kontekście dostępnych metod w mapie w stosunku do listy, jest metoda any(), która zwraca wartość logiczną true/false, jeśli co najmniej jedna para spełnia warunek zdefiniowany w domknięciu.
 groovy:000> jezyki.any { jezyk, autor -> jezyk =~ "[^A-Za-z]" }
===> true
Z kolei metoda every() sprawdza, czy wszystkie pary spełniają warunek z domknięcia.
 groovy:000> jezyki.every { jezyk, autor -> jezyk =~ "[^A-Za-z]" }
===> false
Interesującą metodą może wydać się metoda groupBy(), która zwraca mapę z parami, gdzie dla zdefiniowanego w domknięciu klucza przypisane są wartości pierwotnej mapy.
 groovy:000> jezyki.groupBy { it }
===> {C++=Stroustrup={C++=Stroustrup}, Java=Gosling={Java=Gosling}, Lisp=McCarthy={Lisp=McCarthy}}
groovy:000> jezyki.groupBy { jezyk, autor -> jezyk.charAt(0) }
===> {C={C++=Stroustrup}, J={Java=Gosling}, L={Lisp=McCarthy}}
groovy:000> jezyki.groupBy { jezyk, autor -> autor.contains('o') }
===> {true={C++=Stroustrup, Java=Gosling}, false={Lisp=McCarthy}}
Gdyby ktoś wpadł na ciekawy pomysł prezentujący cechy groupBy() chętnie opublikuję go w kolejnej relacji.

Na samo zakończenie rozdziału autor trafnie podsumowuje możliwości kolekcji w Groovy: "working with collections is easier and faster, your code is shorter, and it's fun" (str. 130). Szczególnie warto podkreślić owe "it's fun". Trudno się z tym nie zgodzić.

20 czerwca 2009

Z rozdziału 7. o kolekcjach (listach) z "Programming Groovy"

2 komentarzy
Rozdział 7. "Working with Collections" w "Programming Groovy: Dynamic Productivity for the Java Developer" Venkata Subramaniama omawia korzystanie z tych cech Groovy, które powodują, że praca z kolekcjami (w rozdziale nacisk kładzie się na java.util.List i java.util.Map) staje się jeszcze bardziej przyjemna niż to ma miejsce w Javie.

W Groovy nie ma typów prostych, a ich użycie jest automatycznie zamieniane na ich odpowiednie typy opakowujące, np. char staje się java.lang.Character a int java.lang.Integer. W przypadku kolekcji możemy pokusić się o podobną analogię - użycie czegoś, co może przypominać konstrukcję tablicy sprowadza się do użycia...(jakieś pomysły?)...java.util.ArrayList.
 $ groovysh
Groovy Shell (1.6.3, JVM: 1.6.0_14)
Type 'help' or '\h' for help.
----------------------------------------------
groovy:000> lista = [ 1, 1, 2, 3, 5, 8, 13 ]
===> [1, 1, 2, 3, 5, 8, 13]
groovy:000> lista.class.name
===> java.util.ArrayList
Warto podkreślić deklarację typu tablicowego, który jest de facto listą. Nie korzystamy z konstrukcji przez new z podaniem rozmiaru lub bez, ale z elementami, jak przywyczailiśmy się w Javie. Jakkolwiek pierwsza jest dostępna w Groovy (wciąż przecież możemy pisać w Javie zanim poczujemy klimaty Groovy), to co istotne, konstrukcja z podaniem elementów jest niedozwolona w Groovy ze względu na...istnienie domknięć.
 groovy:000> l2 = new int[5]
===> [I@1270107
groovy:000> l2.class.name
===> [I
groovy:000> l2.size()
===> 5
groovy:000> l2.each { println "$it" }
0
0
0
0
0
===> [I@1270107
groovy:000> l3 = new int[] {1,1,2,3,5}
ERROR org.codehaus.groovy.control.MultipleCompilationErrorsException:
startup failed, groovysh_parse: 1: unexpected token: 1 @ line 1, column 17.
l3 = new int[] {1,1,2,3,5}
^
1 error
at java_lang_Runnable$run.call (Unknown Source)
Właśnie mnie natchnęło na poszukiwanie analogi i tak sobie myślę, że nigdy nie przyszło mi pomyśleć o tablicy jako byciu typem prostym dla listy. W końcu większość list jest właśnie obsługiwana wewnętrznie przez tablice, a cała pozorna (!) prostota z listami wynika z faktu, że chcemy mieć dynamiczną strukturę, która rozszerza się dynamicznie nie dbając o ilość zajmowanej pamięci. Czy ma uzasadnienie stwierdzenie, że robimy to z czystego lenistwa? Komu chciałoby się dbać o rozmiar tablicy (niezwykle efektywne pamięciowo programowanie), kiedy można machnąć ręką na nadmiar miejsca w pamięci zajmowanej przez listę i właśnie ją stosować? Ja robię to z wygody i mam w wielkim poważaniu, że zazwyczaj moja aplikacja zajmuje więcej miejsca w pamięci niż faktycznie potrzebuje. Teraz stałem się bardziej świadomy i będę jeszcze bardziej zestresowany pisząc aplikacje - poza dobrym stylem programowania, o którym trąbi się wokoło, stosowaniu wzorców projektowych, testowaniu, doszła fobia strat pamięci :) Mogłoby się wydawać, że mistrzowie programowania to najbardziej zestresowani ludzie.

"Chodzenie" po liście jest prawie identyczne z przemieszczaniem się po tablicy w Javie. Parafrazując reklamę - owe "prawie" robi różnicę. W Groovy mamy możliwość chodzenia wstecz z indeksami ujemnymi (minus jest wskazaniem, że idziemy od prawej do lewej). Możemy również wypisać elementy z zadanego zakresu (również z minusami). Uwaga na kolejność indeksów!
 groovy:000> lista
===> [1, 1, 2, 3, 5, 8, 13]
groovy:000> lista[-1] == 13
===> true
groovy:000> lista[-3..-1]
===> [5, 8, 13]
groovy:000> lista[-1..-3]
===> [13, 8, 5]
groovy:000> lista[1..3]
===> [1, 2, 3]
groovy:000> lista[3..1]
===> [3, 2, 1]
Kolejnym udogodnieniem w pracy z listami w Groovy jest pobranie podlisty. Należy jednak pamiętać, że zmiana elementu w podliście zmienia listę macierzystą (!) Typem podlisty jest java.util.RandomAccessSubList. Nawet nie wiedziałem, że taka klasa w ogóle istnieje! I kto powiedział, że Groovy to ZUO?! :)
 groovy:000> lista
===> [1, 1, 2, 3, 5, 8, 13]
groovy:000> podlista = lista[0..3]
===> [1, 1, 2, 3]
groovy:000> podlista.class.name
===> java.util.RandomAccessSubList
groovy:000> podlista[0]
===> 1
groovy:000> lista[0]
===> 1
groovy:000> podlista[0] = 5
===> 5
groovy:000> lista[0]
===> 5
Groovy udostępnia metodę each(), która przyjmuje domknięcie, do którego z kolei przekazywany jest element z listy. Jeśli metoda akceptuje pojedynczy parametr wejściowy, to można opuścić nawiasy i wygląda to bardziej groovy. Pamiętamy o tym, co? Domyślnym parametrem wejściowym jest it, ale możemy go nazwać dowolnie. To też pamiętamy, nie?
 groovy:000> lista
===> [5, 1, 2, 3, 5, 8, 13]
groovy:000> lista.each { println "$it" }
5
1
2
3
5
8
13
===> [5, 1, 2, 3, 5, 8, 13]
groovy:000> lista.each { element -> println "$element" }
5
1
2
3
5
8
13
===> [5, 1, 2, 3, 5, 8, 13]
Autor opisuje różnicę między iteratorem wewnętrznym (m.in. w Groovy) a zewnętrznym (m.in. w Javie). Różnica polega na możliwości kontrolowania iteracji i przy zewnętrznym konieczna jest kontrola końca iteracji. W przypadku Groovy mamy do dyspozycji iterator wewnętrzny i nie musimy o nic dbać - po prostu nasze domknięcie zostanie wykonane z każdym elementem spełniającym warunek funkcji wspierającej iterator, np. wspomniana each(). Poza nią mamy reverseEach() (iterowanie wstecz) oraz eachWithIndex() (iterowanie z dodatkowymi licznikami).
 groovy:000> lista
===> [5, 1, 2, 3, 5, 8, 13]
groovy:000> lista.eachWithIndex { element, index -> println " $index: $element" }
0: 5
1: 1
2: 2
3: 3
4: 5
5: 8
6: 13
===> [5, 1, 2, 3, 5, 8, 13]
W ogóle całe iterowanie w Groovy zdaje się być aż nazbyt wyrafinowane (w pozytywnym tego słowa znaczeniu), bo czy kiedykolwiek przyszło nam do głowy, aby iterować po prostu po literach w napisie?
 groovy:000> napis = "Jacek"
===> Jacek
groovy:000> pojedynczeLitery = []
===> []
groovy:000> for (c in napis) {
groovy:001> pojedynczeLitery += c
groovy:002> }
===> null
groovy:000> pojedynczeLitery
===> [J, a, c, e, k]
groovy:000> pojedynczeLitery[1]
===> a
groovy:000> pojedynczeLitery[1].class.name
===> java.lang.String
Więcej w podręczniku użytkownika Groovy w rozdziale Looping. Aż trudno uwierzyć, że w samej dokumentacji Groovy nie korzysta się z operatora dodawania jako "ekscytującej" alternatywy dla metody List.add(). A można przecież i z wykorzystaniem operatora przesunięcia <<.
 groovy:000> napis = "Jacek"
===> Jacek
groovy:000> pojedynczeLitery = []
===> []
groovy:000> for (c in napis) {
groovy:001> pojedynczeLitery << c
groovy:002> }
===> null
groovy:000> pojedynczeLitery
===> [J, a, c, e, k]
Jeśli chcielibyśmy wykonać domknięcie na każdym elemencie kolekcji i otrzymać ponownie kolekcję ze zmodyfikowanymi elementami korzystamy z metody collect().
 groovy:000> napis
===> Jacek
groovy:000> napis.collect { it }
===> [J, a, c, e, k]
groovy:000> napis.collect { it += 1 }
===> [J1, a1, c1, e1, k1]
Możemy zwrócić jedynie elementy z kolekcji, które spełniają zadany warunek z metodą find(). Warunek opisujemy w domknięciu.
 groovy:000> jacek = "Jacek"
===> Jacek
groovy:000> jacek.find { it == 'a' || it == 'e' }
===> a
Zdziwiony/-a wynikiem find()? Jako możliwa odpowiedź niech posłuży kolejny przykład, tyle że teraz na scenę wchodzi findAll().
 groovy:000> jacek = "Jacek"
===> Jacek
groovy:000> jacek.findAll { it == 'a' || it == 'e' }
===> [a, e]
Teraz jasne? find sprawdza kolejne elementy do pierwszego trafienia i kończy działanie, a findAll przechodzi całą kolekcję.

Nic nie stoi na przeszkodzie, aby wykonać kolejną metodę na zwracanej kolekcji. Poprzedni przykład z findAll() połączymy z size(), albo collect() z sum() (przykład z książki).
 groovy:000> jacek.findAll { it == 'a' || it == 'e' }.size()
===> 2
groovy:000> napis = ['Programming', 'In', 'Groovy']
===> [Programming, In, Groovy]
groovy:000> napis.collect { it.size() }.sum()
===> 19
Wyobraźmy sobie takie konstrukcje w Javie. Pewnie nie byłyby bardzo skomplikowane, ale musielibyśmy pisać je sami. Warto? Nie, jeśli mamy je bezpośrednio wspierane przez język. A teraz wydają mi się po prostu niezbędne.

Kolejna niezwykłość w Groovy to metoda inject(). Pierwszym parametrem wejściowym jest wartość początkowa, która będzie przekazywana domknięciu z samym elementem kolekcji. Owa wartość początkowa może być zmieniana, np. zwiększana/zmniejszana i każdorazowo zostanie przekazana domknięciu. Przykład pożądany, nieprawdaż?
 groovy:000> napis = ['Programming', 'In', 'Groovy']
===> [Programming, In, Groovy]
groovy:000> napis.inject(0) { poprzedniaWartosc, element -> poprzedniaWartosc + element }
===> 0ProgrammingInGroovy
groovy:000> napis.inject(0) { poprzedniaWartosc, element -> poprzedniaWartosc + element.size() }
===> 19
Łączenie elementów kolekcji to robota dla join() z pojedynczym parametrem - separatorem (łącznikiem).
 groovy:000> napis
===> [Programming, In, Groovy]
groovy:000> napis.join(' ')
===> Programming In Groovy
groovy:000> napis.join('+')
===> Programming+In+Groovy
Jako przykład wprost stworzonego dla join przykładu jest dynamiczne tworzenie ścieżek na systemie plików lub klas.
Metoda reverse() odwraca kolejność elementów, a odejmowanie jest usuwaniem elementów z kolekcji, ale nie, nie tylko pojedynczego, ale również każdego z elementów listy-odjemnika.
 groovy:000> napis
===> [Programming, In, Groovy]
groovy:000> napis - ['In', 'Element, ktory nie istnieje', 'Groovy']
===> [Programming]
Możemy wykonać wiele operacji z iteratorem (metoda z domknięciem), albo skorzystać z operatorem spread - * (gwiazdka), który wykonuje metodę, na każdym elemencie kolekcji. Przypomina to w działaniu collect i okazuje się, że są rzeczywiście synonimami (patrz Spread Operator (*.)).
 groovy:000> napis.size()
===> 3
groovy:000> napis*.size()
===> [11, 2, 6]
groovy:000> napis.collect { it.size() }
===> [11, 2, 6]
Na zakończenie przykład zastosowania operatora spread do rozszczepiania listy na pojedyncze parametry wejściowe.
 groovy:000> def funkcja(a, b, c, d) {
groovy:001> println "$a $b $c $d"
groovy:002> }
===> true
groovy:000> napis
===> [Programming, In, Groovy]
groovy:000> funkcja(*napis)
ERROR groovy.lang.MissingMethodException: No signature of method: groovysh_evaluate.funkcja() is applicable for
argument types: (java.lang.String, java.lang.String, java.lang.String) values: [Programming, In, Groovy]
at groovysh_evaluate.run (groovysh_evaluate:2)
...
groovy:000> napis = ['Czyz', 'Groovy', 'Nie', 'Zachwyca?']
===> [Czyz, Groovy, Nie, Zachwyca?]
groovy:000> funkcja(*napis)
Czyz Groovy Nie Zachwyca?
===> null
Liczba elementów w liście musi być zgodna z liczbą parametrów wejściowych metody. Czyż Groovy nie zachwyca? A podobno są to funkcjonalności języków funkcyjnych, więc...pora na naukę języka funkcyjnego na JVM. Propozycje? I tylko proszę nie wyjeżdżać ze Scala :) Jakoś bliżej mi do Clojure. Ktoś się już z clojure zaprzyjaźnił?. Można o nim poczytać w Learning Clojure na WikiBooks, a nawet po polsku na clojure.pl. Pewnie niedługo i tu. Chętni wesprzeć mnie literacko? Przykłady użycia clojure (czy innych języków funkcyjnych) w projektach mile widziane.

19 czerwca 2009

50. spotkanie Warszawa JUG - "Spring Roo" Andrzeja Wisłowskiego

3 komentarzy
Warszawska Grupa Użytkowników Technologii Java (Warszawa JUG) zaprasza na 50. spotkanie, które odbędzie się w najbliższy wtorek, 23.06.2009 o godzinie 18:00 w sali 5440 Wydziału MIMUW przy ul. Banacha 2 w Warszawie.

Temat prezentacji: Spring Roo
Prelegent: Andrzej Wisłowski

Plan prezentacji obejmuje wprowadzenie do nowego projektu SpringSource Spring Roo opisującą proponowane podejście do generowania kodu i uproszczenia życia programistom webowym. Następnie przedstawione zostanie jak łatwo można stworzyć aplikację webową przy pomocy Spring Roo.

Andrzej Wisłowski jest absolwentem Wydziału MIMUW. Programista Java od 5 lat, pasjonat Spring Framework. Twórca list życzeń na merlin.pl. Prywatnie miłośnik gór.

Planowany czas prezentacji to 1,5 godziny, po której planuje się 15-30-minutową dyskusję.

Wstęp wolny!

Zapraszam w imieniu prelegenta i grupy Warszawa JUG!

18 czerwca 2009

Rozdział 6. o napisach w Groovy z "Programming Groovy"

6 komentarzy
Rozdział 6. "Working with Strings" dotyczy obsługi literałów i ciągów znakowych (napisów). Tworzymy literał znakowy w Groovy za pomocą pojedynczych cudzysłowów, np. 'cześć', a napisy z podwójnymi, np. "cześć". W przeciwieństwie do Javy, w Groovy 'a' i "a" są tymi samymi ciągami znaków typu String.
 groovy:000> a1 = 'a'
===> a
groovy:000> a2 = "a"
===> a
groovy:000> a1.class.name
===> java.lang.String
groovy:000> a2.class.name
===> java.lang.String
Jeśli chcielibyśmy stworzyć egzemplarz dla pojedynczego znaku (w Javie jako char lub java.lang.Character) wystarczy skorzystać z konstrukcji as char.
 groovy:000> a1 = 'a'
===> a
groovy:000> a1.class.name
===> java.lang.String
groovy:000> a3 = a1 as char
===> a
groovy:000> a3.class.name
===> java.lang.Character
Przypominam, że nie istnieją typy proste w Groovy, a ich użycie to w rzeczywistości utworzenie egzemplarza typu opakowującego, np. dla char będzie to java.lang.Character.

W ramach pojedynczych cudzysłowów możemy umieścić podwójne.
 groovy:000> a4 = '"a"'
===> "a"
groovy:000> a4.class.name
===> java.lang.String
Różnica między literałem a napisem jest w rozwiązywaniu wyrażeń ${}.
 groovy:000> wartosc = 5
===> 5
groovy:000> tekstJakoLiteral = 'Podano wartosc: ${wartosc}'
===> Podano wartosc: ${wartosc}
groovy:000> tekstJakoNapis = "Podano wartosc: ${wartosc}"
===> Podano wartosc: 5
Napisy są niezmienne (niemodyfikowalne, ang. immutable), więc zmiana ich wartości jest niemożliwa (chyba, że korzystają z domknięć zczytujących dane ze środowiska, ale sam napis jest niezmienny). Pamiętam, że rozgłaszałem coś zupełnie odwrotnego na ostatniej mojej prezentacji Groovy i Grails we Wrocławiu za co przepraszam. Tutaj nic się nie zmienia w stosunku do Javy.

Odczyt z literału bądź napisu jest poprzez symbol tablicowy [].
 groovy:000> napis = "Ala ma kota"
===> Ala ma kota
groovy:000> napis[0]
===> A
groovy:000> napis[0..3]
===> Ala
Specjalne znaki w napisach, np. $ (dolar) możemy pozbawić ich specjalności przez poprzedzenie ich ukośnikiem lewym '\' (ang. backslash), np. \$, albo przez skorzystanie z kolejnej konstrukcji do tworzenia specjalnych napisów przez użycie ukośnika prawego '/' (ang. slash).
 groovy:000> dolary = 500
===> 500
groovy:000>
groovy:000> napis = "Za 1 PLN placimy ${dolary}\$"
===> Za 1 PLN placimy 500$
groovy:000> napisUkosnikPrawy = /Za 1 PLN placimy ${dolary}$/
===> Za 1 PLN placimy 500$
groovy:000> dolary = 200
===> 200
groovy:000> napis
===> Za 1 PLN placimy 500$
groovy:000> napisUkosnikPrawy
===> Za 1 PLN placimy 500$
groovy:000> napisUkosnikPrawy = /Za 1 PLN placimy ${-> dolary}$/
===> Za 1 PLN placimy 200$
groovy:000> dolary = 10
===> 10
groovy:000> napisUkosnikPrawy
===> Za 1 PLN placimy 10$
Jak widać w przykładzie wyżej, Groovy rozwiązuje wartości zmiennych w napisach natychmiast podczas ich tworzenia (gorliwie), a opóźniony (leniwy) odczyt jest możliwy przez zastosowanie...domknięć (bezparametrowych).

Typem dla napisów z wyrażeniami ${} w Groovy jest GString (ang. Groovy string). Groovy inteligentnie przypisuje typ do naszych potrzeb.
 groovy:000> (/napis/).class
===> class java.lang.String
groovy:000> zmienna = 5
===> 5
groovy:000> (/napis ze zmienna ${zmienna}/).class
===> class org.codehaus.groovy.runtime.GStringImpl
groovy:000> "kolejny napis".class
===> class java.lang.String
groovy:000> "kolejny napis, ale juz ze zmienna ${zmienna}".class
===> class org.codehaus.groovy.runtime.GStringImpl
Połączenie GString i domknięć pokazuje jak Groovy traktuje domknięcia. Domknięcie to po prostu blok wykonywalny, który również może być przypisany do zmiennej, a użyty wprost będzie przypisany do zmiennej anonimowej.
 groovy:000> wartosc = 4
===> 4
groovy:000> napis = "Wypiszemy napis wykonujac domkniecie ${-> wartosc}"
===> Wypiszemy napis wykonujac domkniecie 4
groovy:000> napis = "Wypiszemy napis wykonujac domkniecie $wartosc"
===> Wypiszemy napis wykonujac domkniecie 4
// definiuję domknięcie
groovy:000> domkn = {-> wartosc}
===> groovysh_evaluate$_run_closure1@f268de
// i buduję napis ze zmienną, która jest domknięciem
groovy:000> napis = "Wypiszemy napis wykonujac domkniecie $domkn"
===> Wypiszemy napis wykonujac domkniecie 4
W Groovy napisy składające się z wielu linii budujemy z potrójnym podwójnym cudzysłowem i potrójnym pojedynczym cudzysłowem. Pięknie napisane, co? Innymi słowy, trzy razy piszemy pojedynczy cudzysłów, jeśli nie korzystamy z wyrażeń, a z nimi budujemy napis opakowany trzema podwójnymi.
 groovy:000> napisBezZmiennychNaKilkaLinii = '''Zaczynam pisac,
groovy:001> Linia 2
groovy:002> Linia 3 i kilka innych dodatkow, np. wyrazenie: $wartosc'''
===> Zaczynam pisac,
Linia 2
Linia 3 i kilka innych dodatkow, np. wyrazenie: $wartosc
groovy:000> napisZeZmiennymiNaKilkaLinii = """Linia 1
groovy:001> Linia 2
groovy:002> Linia 3 i zmienna $wartosc"""
===> Linia 1
Linia 2
Linia 3 i zmienna 4
Przydatne chociażby do tworzenia plików XML, albo, jak to ujął autor, "Groovy even makes it easy to spam! (Hey, I'm just kidding.)" (str. 109). Żartowniś! :)

W Groovy mamy przeciążanie operatorów, więc odejmowanie w napisach to wycinanie napisu. Dodano metodę minus() do typu String i możemy ciąć.
 groovy:000> napis = "Ala ma kota"
===> Ala ma kota
groovy:000> napis -= "ma"
===> Ala kota
Możemy również iterować po alfabecie, co w połączeniu z napisami daje niesamowity efekt iterowania po napisach.
 groovy:000> for (str in 'ala'..'alg') {
groovy:001> print "$str "
groovy:002> }
ala alb alc ald ale alf alg ===> null
Groovy rozszerza klasę String o udostępnienie metod do pracy z wyrażeniami regularnymi. W ten sposób poza replaceFirst() oraz replaceAll() mamy ~ (tylda) (odpowiada funkcji negate()), który tworzy egzemplarz wzorca, co w połączeniu z napisami tworzonymi przez ukośniki przypomina Perla.
 groovy:000> wzorzec = ~/\w*/
===> \w*
groovy:000> wzorzec.class.name
===> java.util.regex.Pattern
groovy:000> "Ala ma kota" =~ wzorzec
===> java.util.regex.Matcher[pattern=\w* region=0,11 lastmatch=]
groovy:000> matcher = "Ala ma kota" =~ wzorzec
===> java.util.regex.Matcher[pattern=\w* region=0,11 lastmatch=]
groovy:000> matcher.size()
===> 6
groovy:000> matcher[0]
===> Ala
groovy:000> matcher[1]
===>
groovy:000> for (wyraz in matcher) {
groovy:001> println "$wyraz"
groovy:002> }
Ala

ma

kota

===> null
Nawet, jeśli nie zaczniemy pisać rozwiązań przemysłowych w Groovy, to do skryptów jest bezkonkurencyjny. Zamiast Perla możemy wykorzystać JVM z Groovy - w chwilach słabości zawsze możemy zejść na poziom Javy.

Warto również przyjrzeć się różnicy między ==~ a =~. Pierwszy z nich sprawdza pełne dopasowanie, podczas gdy drugi jedynie częściowe.
 groovy:000> matcher = "Ala ma kota" ==~ /ma/
===> false
groovy:000> matcher = "Ala ma kota" =~ /ma/
===> java.util.regex.Matcher[pattern=ma region=0,11 lastmatch=]
groovy:000> matcher = "Ala ma kota" ==~ /Ala \w* kota/
===> true
Niestety nie udało mi się napisać wyrażenia regularnego w stylu /\w{6}/ i nie wiem, dlaczego przykład poniżej zwraca false.
 groovy:000> matcher = "Ala ma kota" ==~ /\w{6}/
===> false
Ma ktoś pomysł? Chętnie poznam rozwiązanie (i później będę się nim chwalił jak swoim :)). Na koniec przykład z książki (str. 113):
 groovy:000> str = 'Groovy is groovy, really groovy'
===> Groovy is groovy, really groovy
groovy:000> (str =~ /groovy/).replaceAll('hip')
===> Groovy is hip, really hip
Jestem zauroczony!

p.s. W komentarzu do wczorajszego wpisu Dokończenie rozdziału 5. o domknięciach z "Programming Groovy" pedro podniósł temat sprawdzenia, czy domknięcie jest faktycznie domknięciem. Odpowiedź w postaci przykładu.
 groovy:000> def funkcja(domkniecie) {
groovy:001> if (domkniecie && domkniecie instanceof Closure) { return domkniecie() }
groovy:002> println "Parametr wejsciowy typu: ${domkniecie.class.name}"
groovy:003> }
===> true
groovy:000> funkcja { "Jestem domknieciem!" }
===> Jestem domknieciem!
groovy:000> funkcja "Jestem jedynie napisem"
Parametr wejsciowy typu: java.lang.String
===> null
Dodałem sprawdzenie z wykorzystaniem instanceof.

17 czerwca 2009

Dokończenie rozdziału 5. o domknięciach z "Programming Groovy"

2 komentarzy
seban uprzedził mnie z pytaniem, które dotyczy domknięć, których dokończenie omówienia zaplanowałem właśnie na ten wpis. To tak, jakby kolejny raz materializowało się powiedzenie "Great minds think alike" :) Jeśli dobrze zrozumiałem pytanie, to chodziło o możliwość sprawdzenia, czy metodzie/domknięciu faktycznie przekazano domknięcie. W końcu nikt z nas nie chciałby skończyć swoich programistycznych doświadczeń z Groovy z NPE.
 $ groovysh
Groovy Shell (1.6.3, JVM: 1.6.0_14)
Type 'help' or '\h' for help.
-----------------------------------------------------------------------------------
groovy:000> def funkcja(domkniecie) {
groovy:001> domkniecie()
groovy:002> }
===> true
groovy:000> funkcja()
ERROR java.lang.NullPointerException: Cannot invoke method call() on null object
at groovysh_evaluate.funkcja (groovysh_evaluate:3)
at groovysh_evaluate.run (groovysh_evaluate:2)
...
groovy:000> funkcja { "cialo domkniecia" }
===> cialo domkniecia
seban wspomniał w komentarzu, że "w Ruby jest metoda block_given? która zwraca true jeśli do metody jest dodany blok i false jeśli nie" i podobną funkcjonalność osiągniemy z prawdą w Groovy (ang. Grovy truth). Po prostu sprawdzenie z if wystarczy, gdyż w Groovy prawda to m.in. niepusta referencja, czyli w przypadku przekazania domknięcia jest to dokładnie pożądane zachowanie. Zmieniamy treść funkcji funkcja na poniższą i NPE odchodzi w zapomnienie.
 groovy:000> def funkcja(domkniecie) {
groovy:001> if (domkniecie) { return domkniecie() }
groovy:002> println "Domyslna implementacja"
groovy:003> }
===> true
groovy:000> funkcja()
Domyslna implementacja
===> null
groovy:000> funkcja { println "Specjalizowana implementacja" }
Specjalizowana implementacja
===> null
Dzięki temu możemy stworzyć metodę z domyślną implementacją (poza if'em), a jeśli przekazane zostanie domknięcie, to ono zostanie wykonane.

Co mnie najbardziej zaintrygowało w działaniu domknięć była możliwość wiązania parametrów na stałe - currying (możnaby pokusić się o tłumaczenie zwijanie funkcji). Najpierw przykład.
 groovy:000> d = { p1, p2 -> "Wywolano z parametrami p1: $p1 oraz p2: $p2" }
===> groovysh_evaluate$_run_closure1@af56a9
Domknięcie akceptuje dwa parametry na wejściu. Nie podano typu parametrów, więc domyślnie jest to cokolwiek. Równie dobrze możemy wykonywać d(1,2) jak i d(1, "cos"). Jeśli teraz założyć sytuację, w której chcielibyśmy wykonać domknięcie kilkakrotnie z tym samym parametrem/-ami, to pojawia się pytanie, czy istnieje sposób, aby przesłonić je czymś krótszym i zaniechać podawania tych samych parametrów w kółko? W Javie możnaby stworzyć funkcję, która wykona funkcję docelową ze związanymi parametrami, ale to jest statyczne wiązanie, a potrzebujemy dynamicznego (w końcu to Groovy - język dynamiczny). I Groovy ma coś do zaoferowania w tej materii (zresztą, czy pisałbym o tym, gdyby nie?! :)). Wystarczy skorzystać z funkcji curry() domknięcia, której parametrami wejściowymi są początkowe wartości parametrów wejściowych domknięcia.
 groovy:000> d1 = d.curry(1)
===> org.codehaus.groovy.runtime.CurriedClosure@1c57ab5
groovy:000> d1 2
===> Wywolano z parametrami p1: 1 oraz p2: 2
groovy:000> d1 5
===> Wywolano z parametrami p1: 1 oraz p2: 5
Innymi słowy, curry() tworzy alias (przesłonięcie) docelowej funkcji związując parametry na stałe z domknięciem. To chyba zapożyczenie z języków funkcyjnych, co? Zdaje się, że właśnie cechy języków funkcyjnych są jedynym ratunkiem dla ożywienia dosyć skostniałego myślenia programistów javowych. Jakoś czuję się odświeżony z nimi :)

Ostatnią cechą domknięć przedstawianą w tym rozdziale jest możliwość delegowania wywołań w domknięciach przez referencje this, owner i delegate (w zasadzie to ta ostatnia ma znaczenie, reszta zdaje się być szumem informacyjnym). Domyślnie delegate jest ustawione na owner, czyli wykonanie metod w ramach domknięcia to przekazanie ich wykonania do obiektu this, a później delegate. Jeśli zmienimy to przypisanie możemy zacząć dobrą imprezkę w Groovy (nie jakieś tam lalala, ale prawdziwy hard-core, taki z czasów prawdziwego HCSE - S.O.D. albo M.O.D., 7 Seconds czy podobnie). Na razie warto jedynie pamiętać, że coś takiego istnieje i można zapewnić sobie tzw. job security, czyli "nikt Cię z Twojej posadki nie ruszy, bo nikt nie wie, co stworzyłeś, a ktoś musi to utrzymywać". To argument nie do odrzucenia za użyciem Groovy czy Grails w kolejnym projekcie :D

16 czerwca 2009

Rozdział 5. o domknięciach z "Programming Groovy"

3 komentarzy
Rozdział 5. "Using Closures" dedykowany jest jednemu z podstawowych wyróżników Groovy - domknięciom. Jest to "one of the Groovy features you'll use the most" (str. 81). A jeśli dodać do tego Grails, to bez domknięć ani rusz. Właśnie to był mój podstawowy powód, dla którego sięgnąłem po Groovy - język programowania w Grails i domknięcia, o których swego czasu trąbiło się wszędzie w związku z Java 7. W Javie 7 domknięć nie będzie, ale to nie ma zupełnie znaczenia skoro są już w Groovy. Domknięcia mogą przypominać wskaźniki do funkcji znanych z C.

Domknięcie w Groovy jest niczym innym, jak anonimowym blokiem kodu, który może mieć parametry wejściowe, zwracać wartość oraz korzystać ze zmiennych zadeklarowanych w kontekście jego wykonania (nie definicji), tzw. zmienne wolne (ang. free variables). Domknięcie możemy przypisać do zmiennej i traktować jako funkcję (wspomniany wskaźnik funkcji to właśnie referencja). Liczę na intuicję :)
 $ groovysh
Groovy Shell (1.6.3, JVM: 1.6.0_14)
Type 'help' or '\h' for help.
------------------------------------------------------------------------------------------------------
groovy:000> dmkncie = { println "$zmienna" }
===> groovysh_evaluate$_run_closure1@3ca754
groovy:000> dmkncie()
ERROR groovy.lang.MissingPropertyException: No such property: zmienna for class: groovysh_evaluate
at groovysh_evaluate$_run_closure1.doCall (groovysh_evaluate:2)
at groovysh_evaluate$_run_closure1.doCall (groovysh_evaluate)
at groovysh_evaluate.run (groovysh_evaluate:2)
...
groovy:000> zmienna = 1
===> 1
groovy:000> dmkncie()
1
===> null
groovy:000> zmienna = 3
===> 3
groovy:000> dmkncie()
3
===> null
groovy:000>
Dla bardziej dociekliwych, żądnych poznania Groovy od strony formalnej, w dopisku, na pierwszej stronie rozdziału, pojawia się wzmianka o domknięciach w Groovy jako \lambda-wyrażeniach (patrz Rachunek lambda w Wikipedii). Nie miałem wcześniej pojęcia, że "Rachunek lambda jest przydatny do badania algorytmów. Wszystkie algorytmy, które dadzą się zapisać w rachunku lambda, dadzą się zaimplementować na maszynie Turinga i odwrotnie." oraz "Rachunek lambda z typami jest podstawą funkcyjnych języków programowania" (za Wikipedią). Jest to kolejny powód, aby nie tylko czytać książki, ale i je relacjonować z odnośnikami do Wikipedii.

Skoro domknięcia mogą być przypisane jako wartości do zmiennych, to możnaby zapytać, czy domknięcia mogą być parametrami wejściowymi funkcji? Odpowiedź jest twierdząca. W końcu to tylko zmienne, a że mają nietypowe zachowanie, kiedy odniesiemy się do ich wartości z nawiasami, no cóż, takie są :) Domknięcia nie mogą być zdefiniowane bez przypisania ich do zmiennej lub jako parametr wejściowy funkcji.

Jeśli domknięcia są ostatnimi parametrami wejściowymi ich przekazanie do funkcji może przypominać samą definicję funkcji dla programisty javowego (!)
 groovy:000> def wypiszZmiennaIWykonajDomkniecie(zmienna, domkniecie) {
groovy:001> println "zmienna: $zmienna, domkniecie: ${domkniecie()}"
groovy:002> }
===> true
groovy:000> wypiszZmiennaIWykonajDomkniecie(5) { "cialo domkniecia" }
zmienna: 5, domkniecie: cialo domkniecia
===> null
I kolejna ciekawostka z dziedziny teoretycznej informatyki/matematyki - funkcja, która przyjmuje na wejściu inną funkcję lub ją zwraca nazywa się funkcją wyższego rzędu i domknięcie nią jest. Nie pamiętam już, kiedy ten teoretyczny "bełkot" tak mi lekko wchodził do głowy :) Ech, gdyby te wszystkie matematyczne formalizmy wprowadzano tak przyjemnie, jak to ma miejsce na przykładzie Groovy. Może to jest sposób, aby uatrakcyjnić wykłady z teoretycznej informatyki - spróbować połączyć przyjemne (nauka języka, który wykorzystamy do pracy zarobkowej) z pożytecznym (zdobędziemy podstawy teoretyczne, dlaczego i jak języki działają)?! To właśnie lubię w tej książce, jest pisana lekkim piórem z wieloma przykładami oraz teoretycznymi dodatkami. Wszystko to sprawia, że poznawanie nowego języka staje się znacznie łatwiejsze i, co ważniejsze, wciąż pozostaje przyjemnym zajęciem.

Domyślnym parametrem domknięcia jest zmienna it. Możemy zmienić jej nazwę, liczbę parametrów wejściowych lub też pozbawić ich całkowicie. Fajny przykład to ilustrujący znajduje się na stronie 95. lub w pliku UsingClosures/ClosuresParameterTypes.groovy.
 groovy:000> d = { println "$it" }
===> groovysh_evaluate$_run_closure1@18f9b75
groovy:000> d 5
5
===> null
groovy:000> d(5)
5
===> null
groovy:000> d = { zmienna -> println "zmienna: $zmienna" }
===> groovysh_evaluate$_run_closure1@1c4a5ec
groovy:000> d 5
zmienna: 5
===> null
groovy:000> d(5)
zmienna: 5
===> null
groovy:000> d = { -> println "Brak parametrow wejsciowych" }
===> groovysh_evaluate$_run_closure1@9dd6e2
groovy:000> d
===> groovysh_evaluate$_run_closure1@9dd6e2
groovy:000> d()
Brak parametrow wejsciowych
===> null
groovy:000> d 4
ERROR groovy.lang.MissingMethodException: No signature of method: groovysh_evaluate$_run_closure1.call()
is applicable for argument types: (java.lang.Integer) values: [4]
at groovysh_evaluate.run (groovysh_evaluate:2)
Sprawdzenie liczby parametrów wejściowych i ich typów możliwe jest za pomocą atrybutów maximumNumberOfParameters oraz parameterTypes domknięcia.

Parametry wejściowe domknięcia mogą, ale nie muszą, posiadać określenia typu - zadziała inferencja typów.

Autor omawia wzorzec opakowane wykonania metody (ang. execute around method), gdzie wykonanie metod obiektu jest opakowane wykonaniem innych, np. dbających, aby plik, z którego będziemy czytać był najpierw otwarty, a później zamknięty zdejmując tym samym obowiązek dbania o pewien ustalony porządek z programisty (i pozwoli mu się tym samym wyszaleć programowaniem "biznesowym", tj. realizacją funkcjonalności aplikacji zamiast jakimiś niskopoziomowymi konstrukcjami niewidocznymi dla końcowego klienta). Domknięcia powodują, że jest to niezwykle proste i wciąż czytelne. Zamiast:
 def z = new Zasob()
z.otworz()
// skoro mamy otwarty zasób możemy wykonywać na nim różne operacje
z.czytaj()
z.pisz()
// na koniec obowiązkowe zamknięcie zasobu i tym samym zwolnienie zajmowanej przez niego pamięci
z.zamknij()
możemy zdefiniować statyczną metodę w klasie Zasob, której zadaniem będzie przygotować właściwe środowisko wykonania:
 def static stworzWlasciweSrodowisko(domkniecie)
{
def z = new Zasob()
try
{
z.otworz()
domkniecie(z)
}
finally
{
z.zamknij()
}
}
W ten sposób tworzymy właściwy kontekst wykonania metod na zasobie bez konieczności poznawania, jak ten kontekst konstruować.
 Zasob.stworzWlasciweSrodowisko { zasob ->
zasob.czytaj()
zasob.pisz()
}
Zdecydowanie łatwiejsze w użyciu i późniejszym utrzymaniu, co?

15 czerwca 2009

Wrażenia po Groovy i Grails na Wrocław JUG

1 komentarzy
Dzisiejszy dzień spędziłem na dwóch wystąpieniach we Wrocławiu. Dzięki uprzejmości mojej firmy mogłem połączyć przyjemne z pożytecznym (pozostawię dla siebie, co było jednym, a co drugim :)). W południe sesja o rozwiązaniach produktowych IBM z zakresu SOA, gdzie przedstawiłem produkty z rodziny WebSphere, a o 18-tej zaprezentowałem Groovy i Grails (ze znaczną przewagą tego pierwszego) w ramach 5. spotkania Wrocław JUG. Frekwencja dopisała. Aktywność uczestników również. I nie byłbym sobą, gdybym nie podkreślił udziału kilku kobiet (i znowu mi się oberwie, że się dziwię i takie tam). Jeśli do tego dodać, że jedna z uczestniczek miała już za sobą programowanie w Groovy, a kolejna w .Net z C# trzeba przyznać, że byłem równie zdziwiony tym faktem, jak niejeden uczestnik możliwościami Groovy :) A zdaje się, że wielu wzbraniało się przez nimi, jak diabeł wody święconej.

Nie zamierzałem robić kolejnego slajdowiska i postanowiłem przejść przez cechy Groovy i Grails z dużym udziałem kodu tworzonego na żywo. Czego mi brakowało to spójnego tworzenia pewnej, bogatej funkcjonalnie aplikacji webowej, która mogłaby poprowadzić uczestników przez cechy obu rozwiązań w sposób usystematyzowany i spójny. Mam nad czym popracować. Dyskusja była, argumenty za i przeciw Groovy też i był Krzysiek Kowalczyk, który niestrudzenie naprowadza mnie na właściwą drogę zrozumienia zawiłości technologicznych przez komentarze do wpisów na moim blogu. Dzięki Krzysiek za pomoc przed i w trakcie spotkania, szczególnie sprowadzeniu mnie na ziemię z tą inferencją typów i "kaczym typowaniem". Doczytam i kolejnym razem będę bardziej precyzyjny. Dziękuję Pawłowi Zubkiewiczowi z wrocławskiego JUGa za zorganizowanie spotkania, które już zdążył skomentować na swoim blogu. Już dawno nie uczestniczyłem w tak intrygujących dyskusjach z tyloma uczestnikami. Ach, byłbym zapomniał - wybacz Grzesiek! Miałem okazję poznać Grzegorza Białka, którego eclipsowe wojaże śledzę już od dawna. Było kilka innych osób, które poznałem, ale naturalnie poza imionami nie mam namiarów na ich bytność w Sieci, więc wciąż pozostaną anonimowi.

Prezentacja dostępna jest jako JacekLaskowski-WroclawJUG-GroovyGrails-15.06.2009.pdf.

Jeśli wiedzy o Groovy i Grails było mało, i intrygują Cię ich możliwości zapraszam na COOLuary v.2. Będzie tam także Scala, która ma się pojawić na kolejnym wrocławskim JUGu. Zdaje się, że Wrocław lekko ożył z zimowego letargu (i po zakończonej magisterce Pawła Szulca :)). Tylko czekać, kiedy na mapie polskich imprez javowych pojawi się pierwsza konferencja zorganizowany przez Wrocław JUG. Wystarczy pojemna sala i rzutnik. Reszta jest. Może kontynuacja COOLuarów we Wrocku?

14 czerwca 2009

Rozdział 4. "Dynamic Typing" z "Programming Groovy" i moje poniedziałkowe wystąpienie o Groovy i Grails we Wrocławiu

2 komentarzy
Wracam do relacji z lektury "Programming Groovy: Dynamic Productivity for the Java Developer" Venkata Subramaniama. Rozdział 4. "Dynamic Typing" rozpoczyna się rozważaniami dotyczącymi natury języków programowania - podział na języki statyczne vs dynamiczne i silna vs słaba typizacja. Nigdy nie byłem mocny w tych podziałach, a dopiero niedawno udało mi się zrozumieć podział między języki imperatywne vs funkcyjne. Wydawałoby się, że wcześniejsze programowanie w języku z każdej kategorii powinno dać mi jasny obraz, ale może dlatego, że owe programy były od czapy, takie nieżyciowe, nie dane mi było zrozumieć tej różnicy wcześniej. Stąd ten wstęp, który mogłoby wydawać się lekko nie na temat, jest dla mnie jak najbardziej na temat. Groovy jest językiem dynamicznym z silną typizacją. Nawet jeśli nie podajemy typu zmiennej, to dzięki inferencji typów (ang. duck typing) wiadomo, z czym mamy do czynienia. Ważne, aby pamiętać, że to co wiadomo może się w trakcie działania aplikacji zmienić (!) Coś na przekór "nie wszystko złoto co się świeci". Jeśli założymy, że złoto to jest coś co się świeci, podczas uruchomienia aplikacji Groovy i piryt może być złotem - wystarczy, że w międzyczasie nadamy mu ów blask, dynamicznie.

Pierwszą zauważalną zaletą dynamicznego typowania jest mniejsza liczba wystukanych klawiszy, aby napisać kompletną aplikację - zero konieczności określania typów podczas definicji zmiennej. Pojawia się pojęcie "Design by Capability", który jest jakby uzupełnieniem "Design by Contract". Tym razem nie interfejs wyznacza kontrakt (jak ma to miejsce w Javie), ale samo istnienie metody. W końcu tylko dlatego implementujemy interfejs, aby ostatecznie posiadać gwarancję, że rozmawiamy z bytem, który posiada określone zachowanie. W Groovy pozbywamy się tego z tzw. "kaczym typowaniem" (ang. duck typing). Tak na prawdę, dla nas, programistów, nie ma znaczenia, czy realizujemy kontrakt przez interfejs czy zestaw publicznych metod - dostosowujemy się do mechanizmów języka, a w Javie się po prostu inaczej nie da (dobra, dobra - da się, bo w końcu mamy Groovy, który jest właśnie aplikacją javową...ekhm...językiem na podwalinach Javy).

Trochę praktyczniej. W Groovy mamy, więc tak:
 def kwacz(byt) {
byt.kwacz()
}

def idz(byt) {
byt.idz()
}

class Kaczka {
void kwacz() {
println "Kwa, kwa, kwa"
}

void idz() {
println "Idę sobie, kwa, kwa, kwa"
}
}

class Czlowiek {
void idz() {
println "Idę sobie pogwizdując"
}
}

idz new Kaczka()
idz new Czlowiek()
I działa! W Javie konieczne byłoby określenie wspólnego interfejsu. W Groovy już nie. Jeśli tylko dany byt udostępnia oczekiwaną metodę wchodzi do gry. Jeśli nie, dopiero próba wykonania metody spowoduje zgłoszenie wyjątku groovy.lang.MissingMethodException.
 groovy> def kwacz(byt) {
groovy> byt.kwacz()
groovy> }
groovy> class Czlowiek {
groovy> }
groovy> kwacz new Czlowiek()

Exception thrown: No signature of method: Czlowiek.kwacz() is applicable for argument types: () values: []

groovy.lang.MissingMethodException: No signature of method: Czlowiek.kwacz() is applicable for argument types: () values: []
at ConsoleScript0.kwacz(ConsoleScript0:2)
at ConsoleScript0$kwacz.callCurrent(Unknown Source)
at ConsoleScript0.run(ConsoleScript0:6)
To musi pociągać za sobą konsekwencje - większą dyscyplinę programowania. Na scenę wchodzą testy. Właśnie nimi rekompensujemy brak silnego typowania znanego z Javy, gdzie kompilator bierze na siebie kontrolę poprawności programu. W Groovy musimy obłożyć naszą aplikację testami, bo bez nich jesteśmy na polu minowym. Aplikacja może działać latami i nic się nie sypnie, aż do tego dnia, kiedy ktoś wywoła nieistniejącą metodę. Bum! Doszukanie się błędu może być nieprzyjemne (uwaga, eufemizm :)).

Sprawdzenie, czy dany obiekt udostępnia daną metodę realizujemy poprzez metaClass i jego metodę respondsTo(), np.
 def kwacz(byt) {
if (byt.metaClass.respondsTo(byt, 'kwacz')) {
byt.kwacz()
}
}
class Czlowiek {}
kwacz new Czlowiek()
Zazwyczaj definiowanie metod w Groovy wiąże się z wykorzystaniem słowa kluczowego def, które określa, że zwracany typ to Object (jak wyżej z metodą kwacz). Jeśli musimy określić zwracany typ innym niż domyślny Object, np. konwencja testów jednostkowych w JUnit (metoda musi być publiczna i zwracać void), to metodę definiujemy identycznie z wymogami w Javie.

Groovy nie posiada typów prostych, jak int czy byte. Wszystko jest obiektem!
 groovy> def x = 1
groovy> println x.class.name
groovy> def y = 1.1
groovy> println y.class.name
groovy> int z = 1
groovy> println z.class.name

java.lang.Integer
java.math.BigDecimal
java.lang.Integer
Dzięki tej cesze, pisanie DSLi (język dziedzinowy, ang. Domain Specific Language) w Groovy jest banalnie proste. Jako przykład autor przedstawia tworzenie jednego, który pozwala na konstrukcje podobne do "5.days.ago.at 4:30" (ale o tym dopiero w rozdziale 18. "Creating DSLs in Groovy").

Na zakończenie rozdziału pojawia się ciekawy przykład z użyciem typów generycznych ala Java Killers Pawła Szulca. Jak to ujął autor (str. 78): "Multimethods fix a problem in Java". Polimorfizm w Javie opiera się na wykonaniu metody z typu obiektu, na który wskazuje referencja, a nie typowi referencji. Wszystko jest pięknie, jeśli metoda w typie potomnym odpowiada deklaracji klasy bazowej/interfejsu. Jeśli jednak poza nią zadeklarujemy bardziej odpowiadającą przekazywanemu typowi jako parametr wejściowy, to i tak nastąpi rzutowanie na typ parametru wejściowego w klasie bazowej/interfejsu. Polimorfizm niepełny? W Groovy obiekt będzie odpytany, czy wspiera metodę o danej sygnaturze. Po konkretne przykłady zapraszam do książki.

Ostatnia sekcja 4.8 "Dynamic: To Be or Not to Be?" to kilka wskazówek odnośnie deklarowania typu obiektu. Jeśli jesteśmy zmuszeni przez dane rozwiązanie, jak JUnit czy odwzorowanie obiektowo-relacyjne (ORM), gdzie typ wskazuje na typ kolumny lub też definiujemy API dla rozwiązań javowych, podajemy typ. W przeciwnym przypadku def wystarczy.

Jeśli temat Cię interesuje, jesteś w poniedziałek 15. czerwca we Wrocławiu i masz chwilę między 18:00 a 20:00 zapraszam na moją prezentację Groovy i Grails "Trochę więcej niż tylko wprowadzenie do Groovy i Grails" w ramach spotkań Wrocław JUG. Postaram się nie zanudzać uczestników teorią, a wypełnić czas praktyką. Zachęcam do aktywnego udziału! Nagrodą będzie wejściówka na JAVArsovię 2009 :)