17 czerwca 2009

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

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