Warszawska Grupa Użytkowników Technologii Java (Warszawa JUG) zaprasza na 56. spotkanie, które odbędzie się we wtorek, 24. listopada o godzinie 18:00 w sali 5440 Wydziału MIMUW przy ul. Banacha 2 w Warszawie.
Temat: Jesienny Eclipse DemoCamp 2009
Prelegenci: Michał Margiel, Łukasz Lenart, Jacek Laskowski, Sebastian Pietrowski, Tomasz Bujok
Tym razem forma spotkania będzie minikonferencją wokół produktów Eclipse Foundation, w której 5 prelegentów zaprezentuje rozwiązania w ciągu...20 minut każdy. Jeśli to nie przekonuje Cię o spędzeniu wtorkowego wieczoru w niezwykle porywającej atmosferze w gronie entuzjastów javowych, to może agenda?
18:00-18:20 Bądź leniwy - wykorzystaj ukrytą moc Eclipse, czyli jak robić by się nie narobić i wycisnąć max ze środowiska Michał Margiel
18:25-18:45 GoogleAppEngine - chmura na Ja(v)wie Łukasz Lenart
18:50-19:10 OpenEJB Eclipse Plugin - EJB 3.1 z Apache OpenEJB w Eclipse IDE Jacek Laskowski
19:15-19:35 Wprowadzenie do Cloud Foundry Sebastian Pietrowski
19:40-20:00 Po prostu monitoring, czyli jak zbudować skalowalną architekturę monitoringu w 20 minut za pomocą Spring, AOP i ActiveMQ Tomasz Bujok
Harmonogram niezwykle napięty, więc o nudzie nie może być mowy. W miłej atmosferze wiedza sama wchodzi do głowy, mimo późnej godziny.
Wstęp wolny, ale rejestracja wskazana.
Zapraszam w imieniu prelegentów i grupy Warszawa JUG!
12 listopada 2009
11 listopada 2009
(-> Clojure zrozumieć-makro-thread przeczytać-artykuł opisać-na-blogu)
Zgodnie z sugestią Michaela zabrałem się za poznawanie Clojure od strony jego kodu źródłowego i podążając słowami mojego mistrza zabrałem się za makro ->, zwane również w kręgach Clojure jako makro "thread".
Kod źródłowy jest niezwykle mały i znajduje się w repozytorium Git - core.clj.
Wykonanie -> z jednym parametrem, to po prostu jego zwrócenie.
Weźmy ponownie coś równie prostego jak poprzednio.
Wciąż jednak nie rozumiałem, co jest takiego specjalnego w tym makro, aż trafiłem na LexParse, gdzie padła wzmianka o potoku (ang. pipeline). Teraz było znacznie łatwiej zrozumieć, co autor miał na myśli. Aczkolwiek do pełnego zrozumienia daleko (pewnie za mało wciąż napisanych programów w Clojure).
"In the end, any program must manipulate state. A program that has no side effects whatsoever is a kind of black box. All you can tell is that the box gets hotter."
i dalej już od autora artykułu: The key is to limit side effects, clearly identify them, and avoid scattering them throughout the code.
Warto również wspomnieć o leniwym rozwiązywaniu/wyliczaniu sekwencji - leniwych sekwencjach (ang. lazy sequences) (a może trafniej będzie nazywać je opóźnionymi czy nawet ospałymi czy w końcu uśpionymi sekwencjami?). Elementy w sekwencji nie są wyliczane, aż do momentu ich użycia, co pozwala na tworzenie nieskończonych ciągów danych bez narażania się na wysycenie sterty pamięci JVM. Ta cecha Clojure uzmysłowiła mi o możliwym podejściu w Javie, w której zwykle (zawsze?) wyliczamy wszystkie elementy, nawet jeśli ich nie potrzebujemy. Weźmy za przykład tablicę 150-elementową, albo jeszcze dłuższą. W Javie, w zależności od typu elementów, wielkość potrzebna na ich przechowanie musi być dostępna w trakcie deklaracji. Alternatywą mogłoby być stworzenie takiej kolekcji, w której podajemy liczbę elementów i tyle, a kiedy potrzebny będzie piąty, dziesiąty, czy którykolwiek inny element, wyliczamy go dokładnie w momencie zapytania o niego. Nie sądzę, abym kiedykolwiek myślał o programowaniu w Javie w ten sposób (może dlatego, że po prostu nie miałem okazji?!). Na szczęście nie musimy już tego implementować w Javie, bo mamy Clojure, więc chociażby dla tego warto zwrócić się ku niemu.
Bardzo przypadł mi do gustu następujący akapit:
Is Clojure code hard to understand? Imagine if every time you read Java source code and encountered syntax elements like if statements, for loops, and anonymous classes, you had to pause and puzzle over what they mean. There are certain things that must be obvious to a person who wants to be a productive Java developer. Likewise there are parts of Clojure syntax that must be obvious for one to efficiently read and understand code.
Podobnie będzie z dowolnym językiem mówionym - angielski prosty, niemiecki również, pewnie podobnie z francuskim, czy hiszpańskim, nie wspominając o rosyjskim czy w ogóle słowiańskich. Nasłuchaliśmy się ich wokoło i teraz po prostu przywykliśmy do ich konstrukcji. Brakuje nam zrozumienia ich semantyki, więc chodzimy na drogie kursy licząc, że cudem bez specjalnego wysiłku mentalnego wejdą nam w przysłowiowe 5 minut. Wtedy również zadamy sobie pytanie o sensowność nauki kolejnego języka skoro mówimy, piszemy i czytamy po angielsku. Przypomina mi to pytanie o sensowność uczenia się Clojure. Po co się go uczyć skoro znamy Javę? Po co uczyć się kolejnego języka mówionego, znając angielski? Dla mnie, odpowiedź nasuwa się sama - z ciekawości, co tracimy nie potrafiąc posługiwać się językiem, którym władają inni. Czy Clojure jest tym językiem, który należałoby poznać? Nie wiem. Wiem jednak, że należy chociażby spróbować zrozumieć programowanie funkcyjne, aby zastosować go tam, gdzie programowanie obiektowe czy imperatywne nie przystaje. Jeśli znamy tylko OO, to jak tu mówić o zdrowym rozsądku? Dobrze ujął to Abraham Maslow:
I suppose it is tempting, if the only tool you have is a hammer, to treat everything as if it were a nail.
bądź, jak to jest częściej przywoływane:
If the only tool you have is a hammer, you tend to see every problem as a nail.
Niestety nie wiem, jak miałoby to brzmieć po polsku :(
Właśnie kiedy miałem opublikować wpis, zajrzałem jeszcze do źródeł Clojure i trafiłem na funkcję clojure-version. Trywialna, aczkolwiek wciąż pouczająca implementacja (której pewnie jeszcze samodzielnie nie dałbym rady napisać):
The -> macro, referred to as the "thread" macro, calls a series of functions, passing the result of each as an argument to the next. For example the following lines have the same result:
(f1 (f2 (f3 x)))
(-> x f3 f2 f1)
Teraz już jest jasne, co makro -> robi. W ramach nauki Clojure pozostaje więc nauczyć się jego implementacji na pamięć, aby kolejnym razem napisać podobne.
Kod źródłowy jest niezwykle mały i znajduje się w repozytorium Git - core.clj.
(defmacro ->Forma doc zwraca Clojure doc, czyli dokumentację funkcji będącej jej argumentem wejściowym, więc i bez kodu źródłowego możnaby się doczytać, co autor miał na myśli tworząc makro -> (mi to nie wystarczyło, ale pomyślałem, że wspomnę i o tej możliwości).
"Threads the expr through the forms. Inserts x as the
second item in the first form, making a list of it if it is not a
list already. If there are more forms, inserts the first form as the
second item in second form, etc."
([x form] (if (seq? form)
`(~(first form) ~x ~@(next form))
(list form x)))
([x form & more] `(-> (-> ~x ~form) ~@more)))
user=> (doc ->)Czytając dokumentację dosłownie, makro -> ma za zadanie wpleść wyrażenie do listy form. Wciąż mi to jednak niewiele mówi. Definicja -> składa się z dwóch przypadków - kiedy makro dostaje na wejściu dwa parametry i więcej. Forma (ang. form) to po prostu dwa nawiasy, w których są wywołania funkcji czy makr, lub ponownie form. I tak rekurencyjnie mamy całą masę par nawiasów. W Clojure *wszystko* jest w nawiasach, albo zastąpione pewnymi specjalnymi konstrukcjami - uproszczeniami rozpoznawanymi przez kompilator, które wychodzą poza ten schemat, np. przywoływana już konstrukcja tworzenia kluczy :nazwa jest równoznaczna z wywołaniem funkcji (keyword "nazwa") (często nazywanymi po angielsku "syntactic sugar").
-------------------------
clojure.core/->
([x form] [x form & more])
Macro
Threads the expr through the forms. Inserts x as the
second item in the first form, making a list of it if it is not a
list already. If there are more forms, inserts the first form as the
second item in second form, etc.
nil
Wykonanie -> z jednym parametrem, to po prostu jego zwrócenie.
user=> (-> 2)To było proste! :) Teraz będzie znacznie trudniej. Jeśli na wejściu -> mamy dwa parametry, to rozpatrujemy przypadki - 1) forma jest sekwencją (seq? zwraca prawdę) i 2) kiedy nie jest. Przy okazji, nazwy funkcji, których wynikiem jest wartość logiczna true/false są zakończone ? (znak zapytania).
2
Weźmy ponownie coś równie prostego jak poprzednio.
user=> ; w nawiasie mamy funkcję minus i jej jedyny argument 3Dlaczego? Analizując kod źródłowy -> dochodzimy do wniosku, że jest to równoznaczne z poniższym.
user=> (-> 2 (- 3))
-1
user=> (- 2 3)Pierwszy argument dla -> jest na drugiej pozycji w pierwszej formie, którą było (- 3). Można jeszcze sprawdzić, jaki był wynik if'a w makro ->.
-1
user=> (seq? (- 3))Zatem wyliczenie (-> 2 (- 3)) sprowadza się do konstrukcji (list form x), tj. (list - 3 2), a to daje:
false
user=> (list - 3 2)Sprawdźmy, co da wywołanie tego funkcją eval.
(#<core$___4506 clojure.core$___4506@16de49c> 3 2)
user=> (eval (list - 3 2))Źle! Chyba popełniłem jakiś błąd w rozumowaniu, jaki argument jest przekazywany do seq?, bo w końcu zgodnie z definicją formy, zapis (- 3) nią jest! Biorąc więc pod uwagę moje niedoświadczenie i możliwe pomyłki w rozumowaniu, wchodzimy w sekcję if'a, bo wtedy wszystko jest cacy - 2 staje się drugim argumentem dla odejmowania, a 3 trzecim. Owe szlaczki w makro to sposób na tworzenie kodu w fazie wczytywania programu w Clojure (zaraz przez kompilacją i uruchomieniem), w której następuje rozwiązywanie makr, których celem jest uproszczenie życia programistom przez generowanie bardziej złożonego kodu (po co klepać kilkakrotnie to samo w wielu liniach, jeśli możemy zdefiniować makro raz i wywoływać wielokrotnie w jednej?! "Cudo" znane programistom C/C++).
1
Wciąż jednak nie rozumiałem, co jest takiego specjalnego w tym makro, aż trafiłem na LexParse, gdzie padła wzmianka o potoku (ang. pipeline). Teraz było znacznie łatwiej zrozumieć, co autor miał na myśli. Aczkolwiek do pełnego zrozumienia daleko (pewnie za mało wciąż napisanych programów w Clojure).
user=> (-> 2 {2 "dwa"} {"dwa" "DWA"})
"DWA"Spróbuj samodzielnie znaleźć odpowiedź dlaczego w wyniku otrzymaliśmy "DWA", albo poniżej true:user=> (-> '(0 0 0) (count) (odd?))Doświadczenia z Clojure skończyłem artykułem Clojure - Functional Programming for the JVM, którego autorem jest R. Mark Volkmann. Tam znalazłem wzmiankę o Simon Peyton-Jones, który włożył wiele wysiłku w Haskella (czysty język funkcyjny) i miał powiedzieć w OSCON 2007 - Simon Peyton-Jones - A Taste of Haskell Part I:
true
"In the end, any program must manipulate state. A program that has no side effects whatsoever is a kind of black box. All you can tell is that the box gets hotter."
i dalej już od autora artykułu: The key is to limit side effects, clearly identify them, and avoid scattering them throughout the code.
Warto również wspomnieć o leniwym rozwiązywaniu/wyliczaniu sekwencji - leniwych sekwencjach (ang. lazy sequences) (a może trafniej będzie nazywać je opóźnionymi czy nawet ospałymi czy w końcu uśpionymi sekwencjami?). Elementy w sekwencji nie są wyliczane, aż do momentu ich użycia, co pozwala na tworzenie nieskończonych ciągów danych bez narażania się na wysycenie sterty pamięci JVM. Ta cecha Clojure uzmysłowiła mi o możliwym podejściu w Javie, w której zwykle (zawsze?) wyliczamy wszystkie elementy, nawet jeśli ich nie potrzebujemy. Weźmy za przykład tablicę 150-elementową, albo jeszcze dłuższą. W Javie, w zależności od typu elementów, wielkość potrzebna na ich przechowanie musi być dostępna w trakcie deklaracji. Alternatywą mogłoby być stworzenie takiej kolekcji, w której podajemy liczbę elementów i tyle, a kiedy potrzebny będzie piąty, dziesiąty, czy którykolwiek inny element, wyliczamy go dokładnie w momencie zapytania o niego. Nie sądzę, abym kiedykolwiek myślał o programowaniu w Javie w ten sposób (może dlatego, że po prostu nie miałem okazji?!). Na szczęście nie musimy już tego implementować w Javie, bo mamy Clojure, więc chociażby dla tego warto zwrócić się ku niemu.
Bardzo przypadł mi do gustu następujący akapit:
Is Clojure code hard to understand? Imagine if every time you read Java source code and encountered syntax elements like if statements, for loops, and anonymous classes, you had to pause and puzzle over what they mean. There are certain things that must be obvious to a person who wants to be a productive Java developer. Likewise there are parts of Clojure syntax that must be obvious for one to efficiently read and understand code.
Podobnie będzie z dowolnym językiem mówionym - angielski prosty, niemiecki również, pewnie podobnie z francuskim, czy hiszpańskim, nie wspominając o rosyjskim czy w ogóle słowiańskich. Nasłuchaliśmy się ich wokoło i teraz po prostu przywykliśmy do ich konstrukcji. Brakuje nam zrozumienia ich semantyki, więc chodzimy na drogie kursy licząc, że cudem bez specjalnego wysiłku mentalnego wejdą nam w przysłowiowe 5 minut. Wtedy również zadamy sobie pytanie o sensowność nauki kolejnego języka skoro mówimy, piszemy i czytamy po angielsku. Przypomina mi to pytanie o sensowność uczenia się Clojure. Po co się go uczyć skoro znamy Javę? Po co uczyć się kolejnego języka mówionego, znając angielski? Dla mnie, odpowiedź nasuwa się sama - z ciekawości, co tracimy nie potrafiąc posługiwać się językiem, którym władają inni. Czy Clojure jest tym językiem, który należałoby poznać? Nie wiem. Wiem jednak, że należy chociażby spróbować zrozumieć programowanie funkcyjne, aby zastosować go tam, gdzie programowanie obiektowe czy imperatywne nie przystaje. Jeśli znamy tylko OO, to jak tu mówić o zdrowym rozsądku? Dobrze ujął to Abraham Maslow:
I suppose it is tempting, if the only tool you have is a hammer, to treat everything as if it were a nail.
bądź, jak to jest częściej przywoływane:
If the only tool you have is a hammer, you tend to see every problem as a nail.
Niestety nie wiem, jak miałoby to brzmieć po polsku :(
Właśnie kiedy miałem opublikować wpis, zajrzałem jeszcze do źródeł Clojure i trafiłem na funkcję clojure-version. Trywialna, aczkolwiek wciąż pouczająca implementacja (której pewnie jeszcze samodzielnie nie dałbym rady napisać):
user=> (clojure-version)Jeśli doczytałeś/-aś do tego miejsca, to może i wytrwałeś/-aś z czytaniem podanego wyżej artykułu o Clojure. Jeśli tak, to w sekcji o mapach (Maps) jest wzmianka o...makrze -> (!)
"1.1.0-alpha-SNAPSHOT"
The -> macro, referred to as the "thread" macro, calls a series of functions, passing the result of each as an argument to the next. For example the following lines have the same result:
(f1 (f2 (f3 x)))
(-> x f3 f2 f1)
Teraz już jest jasne, co makro -> robi. W ramach nauki Clojure pozostaje więc nauczyć się jego implementacji na pamięć, aby kolejnym razem napisać podobne.
10 listopada 2009
Środowisko programistyczne dla Clojure i poprzedni przykład funkcjonalniej
Nasze praktyczne przygody z Clojure możemy zacząć od instalacji wymaganego oprogramowania i/lub uruchomienia Clojure REPL lokalnie, albo skorzystać z Lord of the REPLs. Interesująca alternatywa, nie tylko pomocna w nauce Clojure, ale i Groovy, Ruby czy Scala.
Kiedy jednak nasze doświadczenie w programowaniu funkcyjnym w Clojure zacznie wykraczać poza proste aplikacje będące złożeniem kilku funkcji i spróbujemy wyjść poza ramy REPL możemy skorzystać ze wsparcia udostępnianego przez zintegrowane środowiska programistyczne, np. NetBeans IDE, Eclipse IDE czy IntelliJ IDEA. Wystarczy doszukać się właściwej wtyczki (rozszerzenia) i możemy czerpać garściami z pomocy IDE - możemy chociażby znieść z siebie trud dbania o nawiasy. Dla NetBeans IDE mamy Enclojure. Ostatnia wersja z 3.11, więc dostatecznie świeża, aby uwierzyć w aktywny jej rozwój. Dla środowiska Eclipse IDE mamy Clojure plug-in for Eclipse (clojure-dev), o której dowiedziałem się w artykule The Clojure programming language - Take advantage of the Clojure plug-in for Eclipse. Stawiam na NetBeans IDE i Enclojure.
Dla bloggerów istnieje podświetlanie składni dla Clojure w ramach Syntax Highlighter 2.0 - All Syntax Highlighter 2.0 brushes collected, described and downloadable. Można zobaczyć go w działaniu w moich wpisach dotyczących Clojure, chociażby ten. Jeśli więc przyjdzie zaprezentować swoje cudo w Clojure szerokiej publiczności na własnym blogu warto podeprzeć się stylem clojure. Znacznie uatrakcyjnia prezentację kodu.
A skoro mamy środowisko możemy powrócić do naszego przykładu z surowcami, której wersja bardziej przypominająca wersję funkcjonalną (dosłownie i w przenośni) wygląda teraz następująco:
Zamiast dłubania w REPL zapisujemy cały program w pliku czas-zwiekszenia-produkcji.clj. Zwyczajowo skrypty w Clojure zapisywane są z rozszerzeniem .clj.
Tworzymy kolejny skrypt testowy skrypt-testowy.clj, który zweryfikuje poprawność działania.
W kolejnym podejściu postaram się zrozumieć o co chodzi w makro -> oraz funkcji line-seq z core.clj jak zasugerował Michael w komentarzu do ostatniego mojego wpisu o Clojure - Programowanie w Clojure - część 2 - pierwszy przykład z wyliczaniem surowców.
Kiedy jednak nasze doświadczenie w programowaniu funkcyjnym w Clojure zacznie wykraczać poza proste aplikacje będące złożeniem kilku funkcji i spróbujemy wyjść poza ramy REPL możemy skorzystać ze wsparcia udostępnianego przez zintegrowane środowiska programistyczne, np. NetBeans IDE, Eclipse IDE czy IntelliJ IDEA. Wystarczy doszukać się właściwej wtyczki (rozszerzenia) i możemy czerpać garściami z pomocy IDE - możemy chociażby znieść z siebie trud dbania o nawiasy. Dla NetBeans IDE mamy Enclojure. Ostatnia wersja z 3.11, więc dostatecznie świeża, aby uwierzyć w aktywny jej rozwój. Dla środowiska Eclipse IDE mamy Clojure plug-in for Eclipse (clojure-dev), o której dowiedziałem się w artykule The Clojure programming language - Take advantage of the Clojure plug-in for Eclipse. Stawiam na NetBeans IDE i Enclojure.
Dla bloggerów istnieje podświetlanie składni dla Clojure w ramach Syntax Highlighter 2.0 - All Syntax Highlighter 2.0 brushes collected, described and downloadable. Można zobaczyć go w działaniu w moich wpisach dotyczących Clojure, chociażby ten. Jeśli więc przyjdzie zaprezentować swoje cudo w Clojure szerokiej publiczności na własnym blogu warto podeprzeć się stylem clojure. Znacznie uatrakcyjnia prezentację kodu.
A skoro mamy środowisko możemy powrócić do naszego przykładu z surowcami, której wersja bardziej przypominająca wersję funkcjonalną (dosłownie i w przenośni) wygląda teraz następująco:
(ns pl.jaceklaskowski.clojure)Widać wyraźne zwiększenie konstrukcji PF z reduce i map. Poza samym wyliczeniem, za ile nastąpi zwiększenie (w linii 8.) mamy również wyliczoną dokładną godzinę, kiedy to nastąpi (linie 6., 7. i 9.)
(defn czas-zwiekszenia-produkcji
"Wylicza potrzebny czas (w minutach) do osiagniecia stanu magazynowego na uruchomienie rozbudowy do poziomu przy danej produkcji"
[docelowo, magazyn, produkcja]
(let [teraz (java.util.Calendar/getInstance)]
(. teraz (add (java.util.Calendar/MINUTE)
(* 60 (reduce max (map / (map - (map #(.getValue %) docelowo) (map #(.getValue %) magazyn)) (map #(.getValue %) produkcja))))))
(. teraz getTime)))
Zamiast dłubania w REPL zapisujemy cały program w pliku czas-zwiekszenia-produkcji.clj. Zwyczajowo skrypty w Clojure zapisywane są z rozszerzeniem .clj.
Tworzymy kolejny skrypt testowy skrypt-testowy.clj, który zweryfikuje poprawność działania.
(load-file "czas-zwiekszenia-produkcji.clj")Uruchomienie to po prostu wykonanie:
(ns pl.jaceklaskowski.clojure)
(def magazyn {:drewno 10 :glina 10 :zelazo 10 :zboze 10})
(def docelowo {:drewno 20 :glina 20 :zelazo 20 :zboze 20})
(def produkcja {:drewno 5 :glina 5 :zelazo 5 :zboze 5})
(println (czas-zwiekszenia-produkcji docelowo magazyn produkcja))
$ java -jar clojure-1.1.0-alpha-SNAPSHOT.jar skrypt-testowy.cljI tak kończy się moje poszukiwanie ładniejszej (=bardziej funkcyjnej) wersji czas-zwiekszenia-produkcji. Oczywiście nie kończy to mojej przygody z Clojure i jeśli kiedykolwiek pomyślałem, aby sobie odpuścić, bo i po co mi PF, to wizyta na blogu BEST IN CLASS jest faktycznie najlepszym w swojej klasie i nie pozwala zapomnieć o możliwościach Clojure. Warto tam zajrzeć w wolnej chwili, szczególnie w chwilach zwątpienia. Mnie zachwycił i upewnił w postanowieniu poznania go dokładniej.
#<Date Tue Nov 10 23:36:07 CET 2009>
W kolejnym podejściu postaram się zrozumieć o co chodzi w makro -> oraz funkcji line-seq z core.clj jak zasugerował Michael w komentarzu do ostatniego mojego wpisu o Clojure - Programowanie w Clojure - część 2 - pierwszy przykład z wyliczaniem surowców.
08 listopada 2009
Programowanie w Clojure - część 2 - pierwszy przykład z wyliczaniem surowców
Nasze ćwiczenia z programowania w Clojure zaczniemy od uruchomienia Clojure REPL (Read-Eval-Print-Loop). Clojure REPL jest niczym innym jak interaktywnym interpreterem dla Clojure.
Powiedzmy, że chcemy wyliczyć liczbę surowców do wybudowania pewnej jednostki (w szczególności podniesienia poziomu samego surowca, aby dawał znacznie większą produkcję niż obecnie). Mamy 4 rodzaje surowców, które nazwiemy: drewno, glina, żelazo i zboże. Całkiem typowe w wielu grach strategicznych. Na celownik weźmiemy Traviana. Trochę czasu mi z nim zeszło i zawsze zastanawiałem się, czy jestem w stanie stworzyć system-bota, który podpowiadałby mi najlepszą strategię. Ktoś kiedyś wspominał coś o programowaniu nieliniowym odnośnie tego rodzaju kalkulacji, ale pojawiała się również wzmianka o programowaniu funkcyjnym. Ile w tym prawdy nigdy nie doszedłem, jednakże tym razem mam możliwość choć po części sprawdzić się w Clojure jako języku funkcyjnym do rozwiązania, albo chociaż próby rozwiązania, tego problemu. To byłby taki system ekspertowy, który podpowiadałby mi różne strategie (pewnie za nim stoi cała masa teorii matematycznej z różnymi modelami, ale ja wybieram podejście naiwne bez zebrania wcześniej wystarczającej wiedzy, tj. "na żywioł" :)). Do zbudowania gliny na 1. poziomie (tak, z każdą jednostką mamy związaną cechę - poziom) potrzeba 80 drewna, 40 gliny, 80 żelaza i 50 zboża (patrz 1.1.11 Kopalnia gliny). Każda wioska ma swoją produkcję i zakładam, że początkowo produkcja jest na 0. Najpierw spróbuję odpowiedzieć na pytanie, ile czasu potrzeba, abym podniósł produkcję gliny przy aktualnym stanie magazynu i produkcji surowców.
Mamy więc założenia do funkcji, którą w Javie moglibyśmy nazwać obliczCzasDoZwiekszeniaProdukcji(surowca, naPoziom, przyObecnymStanieMagazynowym, przyObecnejProdukcji). Nazwy funkcji w Clojure są zazwyczaj ciągiem słów oddzielonych myślnikami, co daje w naszym przypadku czas-zwiekszenia-produkcji (zakładając, że celem funkcji jest obliczenie czegoś nie widzę potrzeby powtarzania tego w nazwie funkcji).
Zaczynam od definicji magazynu. Będzie to mapa, która w Clojure deklarowana jest przez {} (nawiasy klamrowe), między którymi ciąg elementów tworzy pary klucz-wartość. Przecinki są opcjonalne i traktowane dokładnie jak spacja, czyli taki zapis
Do definicji funkcji w Clojure używamy defn, w której w cudzysłowach dodajemy opis funkcji, a w nawiasach kwadratowych parametry wejściowe.
Wywołanie funkcji w Clojure jest listą, której pierwszym elementem jest nazwa funkcji, a po nim następują parametry wejściowe.
jlaskowski@work /cygdrive/c/oss/clojureOd tej pory zakładam, że wiemy, w jaki sposób dostać znak zachęty jak wyżej.
$ java -jar clojure-1.1.0-alpha-SNAPSHOT.jar
Clojure 1.1.0-alpha-SNAPSHOT
user=>
Powiedzmy, że chcemy wyliczyć liczbę surowców do wybudowania pewnej jednostki (w szczególności podniesienia poziomu samego surowca, aby dawał znacznie większą produkcję niż obecnie). Mamy 4 rodzaje surowców, które nazwiemy: drewno, glina, żelazo i zboże. Całkiem typowe w wielu grach strategicznych. Na celownik weźmiemy Traviana. Trochę czasu mi z nim zeszło i zawsze zastanawiałem się, czy jestem w stanie stworzyć system-bota, który podpowiadałby mi najlepszą strategię. Ktoś kiedyś wspominał coś o programowaniu nieliniowym odnośnie tego rodzaju kalkulacji, ale pojawiała się również wzmianka o programowaniu funkcyjnym. Ile w tym prawdy nigdy nie doszedłem, jednakże tym razem mam możliwość choć po części sprawdzić się w Clojure jako języku funkcyjnym do rozwiązania, albo chociaż próby rozwiązania, tego problemu. To byłby taki system ekspertowy, który podpowiadałby mi różne strategie (pewnie za nim stoi cała masa teorii matematycznej z różnymi modelami, ale ja wybieram podejście naiwne bez zebrania wcześniej wystarczającej wiedzy, tj. "na żywioł" :)). Do zbudowania gliny na 1. poziomie (tak, z każdą jednostką mamy związaną cechę - poziom) potrzeba 80 drewna, 40 gliny, 80 żelaza i 50 zboża (patrz 1.1.11 Kopalnia gliny). Każda wioska ma swoją produkcję i zakładam, że początkowo produkcja jest na 0. Najpierw spróbuję odpowiedzieć na pytanie, ile czasu potrzeba, abym podniósł produkcję gliny przy aktualnym stanie magazynu i produkcji surowców.
Mamy więc założenia do funkcji, którą w Javie moglibyśmy nazwać obliczCzasDoZwiekszeniaProdukcji(surowca, naPoziom, przyObecnymStanieMagazynowym, przyObecnejProdukcji). Nazwy funkcji w Clojure są zazwyczaj ciągiem słów oddzielonych myślnikami, co daje w naszym przypadku czas-zwiekszenia-produkcji (zakładając, że celem funkcji jest obliczenie czegoś nie widzę potrzeby powtarzania tego w nazwie funkcji).
Zaczynam od definicji magazynu. Będzie to mapa, która w Clojure deklarowana jest przez {} (nawiasy klamrowe), między którymi ciąg elementów tworzy pary klucz-wartość. Przecinki są opcjonalne i traktowane dokładnie jak spacja, czyli taki zapis
user=> (def magazyn {:drewno 0 :glina 0 :zelazo 0 :zboze 0})
#'user/magazynjest równoznaczny takiemu: user=> (def magazyn {:drewno 0, :glina 0, :zelazo 0, :zboze 0})
#'user/magazynW definiowaniu mapy skorzystałem z możliwości definiowania słów kluczowych w Clojure z użyciem : (dwukropek). Wartością słów kluczowych są one same, więc idealnie nadają się na klucze w mapie.user=> :cokolwiekJak większość (wszystko?) w Clojure, tak i mapa jest funkcją, której pojedynczym argumentem jest klucz.
:cokolwiek
user=> (magazyn :drewno)Również odwrotne spojrzenie jest poprawne, tj. słowa kluczowe są również funkcją, której argumentem jest mapa, w której się odszukują (!)
0
user=> (:drewno magazyn)Takie spojrzenie na mapę i jej kluczy było dla mnie niezwykłym doświadczeniem mentalnym :) W sumie to logiczne, aby potraktować w ten sposób struktury danych, a zmienia całkowicie moje dotychczasowe spojrzenie obiektowe. Skoro wszystko jest funkcją, jedynym problemem pozostaje poznanie ich zasady działania (semantyki). Niby proste i pamiętam, że kiedy uczyłem się żonglować, co wydawało mi się początkowo niezwykle trudne i w zasadzie poza zasięgiem, kiedy oduczyłem się złego sposobu podawania piłeczki z ręki do ręki, nauczenie się poprawnie przez przerzucanie piłeczek było dziecinnie proste. Mówi się, że jedynym problemem w nauce nowego są nasze własne przyzwyczajenia i obawy. Chyba dlatego tak trudno jest się uczyć z wiekiem - zbyt wiele doświadczenia, które nas ogranicza?!
0
Do definicji funkcji w Clojure używamy defn, w której w cudzysłowach dodajemy opis funkcji, a w nawiasach kwadratowych parametry wejściowe.
(defn czas-zwiekszenia-produkcjiChwilowo zamiast parametru wejściowego poziom będącym liczbą całkowitą przekażemy mapę z potrzebnymi surowcami do przejścia na dany poziom.
"Wylicza potrzebny czas do zwiekszenia produkcji surowca na danym poziom przy danym stanie magazynowym i produkcji"
[surowiec, poziom, stanMagazynowy, produkcja]
...
)
user=> (def poziom1 {:drewno 80 :glina 40 :zelazo 80 :zboze 50})
#'user/poziom1W takim przypadku sam surowiec nie ma znaczenia, bo w końcu na wejściu mamy stan początkowy (stan magazynowy), stan docelowy i produkcję (przyrost jednostek w czasie 1h). Definicja funkcji mogłaby wyglądać tak:(defn czas-zwiekszenia-produkcjiWszystkie parametry wejściowe są mapami.
"Wylicza potrzebny czas do osiagniecia stanu magazynowego na uruchomienie rozbudowy do poziomu przy danej produkcji"
[stanDocelowy, stanMagazynowy, produkcja]
...
)
Wywołanie funkcji w Clojure jest listą, której pierwszym elementem jest nazwa funkcji, a po nim następują parametry wejściowe.
user=> (czas-zwiekszenia-produkcji docelowo magazyn produkcja)Dodając do tego możliwość importowania typów javowych przez import oraz ich wywoływania przez . (kropka) czy / (ukośnik) otrzymujemy ostatecznie następujący program w Clojure do obliczania czas potrzebnego do zwiększenia produkcji surowca przy danym magazynie i bieżącej produkcji.
(def magazyn {:drewno 10 :glina 4 :zelazo 15 :zboze 0})
(def produkcja {:drewno 5 :glina 2 :zelazo 5 :zboze 4})
(def glina1 {:drewno 80 :glina 40 :zelazo 80 :zboze 5})
(import '(java.util Calendar))
(def teraz (Calendar/getInstance))
(defn czas-zwiekszenia-produkcji
"Wylicza potrzebny czas (w minutach) do osiagniecia stanu magazynowego na uruchomienie rozbudowy do poziomu przy danej produkcji"
[docelowo, magazyn, produkcja]
(max
(/ (- (docelowo :drewno)(magazyn :drewno)) (produkcja :drewno))
(/ (- (docelowo :glina)(magazyn :glina)) (produkcja :glina))
(/ (- (docelowo :zelazo)(magazyn :zelazo)) (produkcja :zelazo))
(/ (- (docelowo :zboze)(magazyn :zboze)) (produkcja :zboze)))
)
(. teraz add (Calendar/MINUTE) (* 60 (czas-zwiekszenia-produkcji glina1 magazyn produkcja)))
(. teraz getTime)Kopiując każdą z linii do REPL mamy:$ java -jar clojure-1.1.0-alpha-SNAPSHOT.jarWersja bardziej funkcyjna z map i reduce w kolejnym wpisie. Zdecydowanie zbyt wiele w tej wersji argumentów do max. Można się zapisać na śmierć z tymi kluczami w mapach i samym wprowadzaniem programu do wykonania. Wasze propozycje mile widziane, bo moja przygoda z programowaniem funkcyjnym z Clojure dopiero się zaczęła i wpadki są wręcz oczekiwane.
Clojure 1.1.0-alpha-SNAPSHOT
user=> (def magazyn {:drewno 10 :glina 4 :zelazo 15 :zboze 0})
#'user/magazyn
user=> (def produkcja {:drewno 5 :glina 2 :zelazo 5 :zboze 4})
#'user/produkcja
user=> (def glina1 {:drewno 80 :glina 40 :zelazo 80 :zboze 5})
#'user/glina1
user=> (import '(java.util Calendar))
java.util.Calendar
user=> (def teraz (Calendar/getInstance))
#'user/teraz
user=> (defn czas-zwiekszenia-produkcji
"Wylicza potrzebny czas (w minutach) do osiagniecia stanu magazynowego na uruchomienie rozbudowy do poziomu przy danej produkcji"
[docelowo, magazyn, produkcja]
(max
(/ (- (docelowo :drewno)(magazyn :drewno)) (produkcja :drewno))
(/ (- (docelowo :glina)(magazyn :glina)) (produkcja :glina))
(/ (- (docelowo :zelazo)(magazyn :zelazo)) (produkcja :zelazo))
(/ (- (docelowo :zboze)(magazyn :zboze)) (produkcja :zboze)))
)
#'user/czas-zwiekszenia-produkcji
user=> (. teraz add (Calendar/MINUTE) (* 60 (czas-zwiekszenia-produkcji glina1 magazyn produkcja)))
nil
user=> (. teraz getTime)
#<Date Mon Nov 09 07:16:59 CET 2009>
05 listopada 2009
55. spotkanie Warszawa JUG - "Zastosowanie BPELa w praktyce" Rafała Rusina i Macieja Próchniaka
Temat prezentacji: Zastosowanie BPEL'a w praktyce przy użyciu Apache ODE, Apache ServiceMix i SoapUI
Prelegenci: Rafał Rusin (TouK), Maciej Próchniak (TouK)
W trakcie prezentacji Rafał i Maciej opowiedzą o połączeniu Apache ODE i Apache ServiceMix. Rozpoczniemy od wstępu do BPELa i jego ideologii oraz prostego przykładu połączenia kilku serwisów ODE z ServiceMixem. Będzie to podstawą do omówienia różnych kwesti, które sprawiły im najwięcej trudności podczas projektu i sposoby ich rozwiązywania. SoapUI znajdzie swoje miejsce w arsenale programisty jako wygodne narzędzie do testowania serwisów.
Rafał Rusin jest absolwentem Uniwersystetu Warszawskiego. W TouK pracuje od 2004 roku. Zajmowałem się developmentem i architekturą wielu komercyjnych projektów w tym czasie. Tematyka procesów biznesowych towarzyszyła mu już w tamym okresie. Ostatnio postanowił zastować projekt Apache ODE do większego projektu komercyjnego, który sprawił, że pojawiło się kilka poprawek i rozszerzeń ODE, co z kolei skończyło się nieoczekiwanym zaproszeniem Rafała jako członka zespołu sterującego Apache ODE PMC.
Maciej Próchniak jest absolwentem matematyki Uniwersystetu Warszawskiego. W poprzednich latach tworzył w Javatechu aplikacje webowe. Obecnie zajmuje się utrzymaniem oraz testowaniem procesów biznesowych napisanych w BPELu, a także architekturą i implementacją komponentów ServiceMix'owych.
Planowany czas prezentacji to 1,5 godziny, po której planuje się 15-30-minutową dyskusję.
Wstęp wolny!
Zapraszam w imieniu prelegentów i grupy Warszawa JUG!
03 listopada 2009
(wady) Agile - Dyskusja
Najwyraźniej ostatnia Warsjawa 2009 (patrz moja relacja Wrażenia z sobotniej, zwinnej Warsjawy 2009) pokazała niedosyt "lekkich" tematów o Scrumie/Agile podczas spotkań i konferencji organizowanych przez Warszawa JUG, bądź też w samej Warszawie. Na forum grupy rozgorzała dyskusja [warszawa-jug] (wady) Agile - Dyskusja, w której moim faworytem stał się nieznany mi wcześniej Adam Lider (nota bene, nazwisko mówi samo za siebie), który napisał (cytuję za pozwoleniem Adama w całości):
Po kilku latach doswiadczenia z lekkimi metodykami jestem sceptycznie nastawiony do Scruma. Uwazam ze jest to metoda, ktora moze nawet wyrzadzic krzywde filozofii Agile. Wytwarzanie oprogramowania jest rzemioslem trudnym, wymagajacym wiedzy, doswiadczenia, a przede wszystkim dyscypliny. Aby dojsc do "mistrzostwa" trzeba po prostu duzo pracy. Natomiast Scrum jest czesto widziany jako rozwiazanie dla zespolow, ktore sobie nie radza. Niestety zdecydowana wiekszosc tych zespolow zbudowana jest z malo lub srednio doswiadczonych ludzi i Scrum moze im niewiele pomoc a nawet zaszkodzic, poprzez koncepcje samoorganizujacego sie zespolu. Jak ja moge wiedziec co jest dla mnie dobre skoro malo jeszcze doswiadczylem? Najczesciej nie wiem. Co wiecej wprowadzenie Scruma do takich zespolow wprowadza rodzaj sztucznego uporzadkowania, w ktorym czesto wieksza wage przewiazuje sie do pielegnacji Scruma niz projektu. Tam gdzie Scrum dziala to najczesniej zespoly zbudowane z bardzo dobrych ludzi, a Scrum jest tylko (bardzo waznym) smarem dla dobrego mechanizmu.Dla zespolow mniej doswiadczonych zdecydowanie bardziej polecam XP. Dla mnie glowna roznica w stosunku do Scruma to zdecyowanie wiekszy nacisk na czysto techniczne praktyki tj. TDD, No bugs, Done Done z podkresleniem, ze musisz stosowac wszystko i nie jutro, ale dzisiaj. Praktykowanie TDD nie jest latwe i wiekszosc mniej doswiadczoncyh zespolow pracujacych pod etykieta Scrum (lub bez) po prostu odklada w czasie zastosowanie tej (lub innych rownie wymagajacych) praktyki (w koncu sami sie organizujemy i skoro to dla nas trudne, mozemy to zaczac pozniej). Jak sie ma kiepskich rzemieslnikow to Scrum niewiele pomoze. To troche tak jak z nasza reprezentacja pilki noznej - czy wierzycie ze z super taktyka super trenera nasi kopacze zdobyliby puchar w 2012? Moze i zagraliby lepiej, a moze wrecz przeciwnie bo np. kluczem dla taktyki bylaby gra z pierwszej pilki, a to trzeba umiec.
Moj glowny zarzut dla tej metody to proba wmowienia, ze wytwarzanie oprogramowania moze byc efektywne i dosc latwe (ze Scrumem), z niedostateczny podkresleniem wagi rzemiosla zespolu i jego czlonkow.
Ta wypowiedź dokładnie wyraża mój pogląd na temat lekkich metodyk, które mimo swojej nazwy nie są wcale lekkimi we wdrożeniu, bo ich lekkość jest zauważalna dopiero, kiedy zespół jest doświadczony, aby pozwolić sobie na samoorganizowanie i dyscyplinę pracy. Skąd niby członkowie młodych zespołów (doświadczeniem nie wiekiem) mieliby wiedzieć, czego chcą, jeśli ich jedyną potrzebą jest skończyć projekt (nic nadzwyczajnego, normalka) w technologi, którą dopiero poznają, bo wszyscy wkoło mówili, że fajna?! Przejaskrawiam z tą nową technologią, ale traktuję go jedynie jako przykład i możnaby tutaj podstawić wszystko inne, które kojarzy nam się z fantazją młodych zespołów. Tutaj podkreślam, że zwykle w takich zespołach panuje przekonanie, że fajna technologia będzie panaceum na brak doświadczenia zespołu oraz skróci czas, tak że projekt nie tylko, że zakończy się w założonym czasie, ale i budżecie (!) Marzenia ściętej głowy, co? Powiedziałbym, że zwinny zespół można budować z osób, które samodzielnie również potrafiliby poprowadzić ten projekt tyle, że zajęłoby im to więcej czasu. Siła doświadczonych inżynierów wynika z ich pragmatycznego podejścia do tematu i świadomości, że po ciekawym poranku z kawką i eclipsową wtyczką do pudelka (ukłon w stronę Mateusza) przyjdzie im ciężko popracować przy zadaniu. Jeśli do tej ciężkiej pracy dodamy niezwykle wyrafinowany funkcjonalnie superszkielet aplikacyjny, to mamy dwa podejścia, idziemy z nim w szranki i konkury, aby go poznać, a wtedy wdrażamy, albo po prostu zarzucamy, bo z naszych obliczeń wynika, że albo on albo my. To nazywam pragmatycznym podejściem. Mamy świadomość, że nie wszystko teraz i zaraz, że niektóre superrozwiązania jeszcze na nas poczekają. Takiej świadomości nie możemy oczekiwać od niedoświadczonych członków zespołu.
A Ty jak uważasz? Pewnie nie tylko ja jestem zainteresowany Twoim zdaniem, więc podejmij temat i wyraź swoją opinię na forum w wątku [warszawa-jug] (wady) Agile - Dyskusja.
Po kilku latach doswiadczenia z lekkimi metodykami jestem sceptycznie nastawiony do Scruma. Uwazam ze jest to metoda, ktora moze nawet wyrzadzic krzywde filozofii Agile. Wytwarzanie oprogramowania jest rzemioslem trudnym, wymagajacym wiedzy, doswiadczenia, a przede wszystkim dyscypliny. Aby dojsc do "mistrzostwa" trzeba po prostu duzo pracy. Natomiast Scrum jest czesto widziany jako rozwiazanie dla zespolow, ktore sobie nie radza. Niestety zdecydowana wiekszosc tych zespolow zbudowana jest z malo lub srednio doswiadczonych ludzi i Scrum moze im niewiele pomoc a nawet zaszkodzic, poprzez koncepcje samoorganizujacego sie zespolu. Jak ja moge wiedziec co jest dla mnie dobre skoro malo jeszcze doswiadczylem? Najczesciej nie wiem. Co wiecej wprowadzenie Scruma do takich zespolow wprowadza rodzaj sztucznego uporzadkowania, w ktorym czesto wieksza wage przewiazuje sie do pielegnacji Scruma niz projektu. Tam gdzie Scrum dziala to najczesniej zespoly zbudowane z bardzo dobrych ludzi, a Scrum jest tylko (bardzo waznym) smarem dla dobrego mechanizmu.Dla zespolow mniej doswiadczonych zdecydowanie bardziej polecam XP. Dla mnie glowna roznica w stosunku do Scruma to zdecyowanie wiekszy nacisk na czysto techniczne praktyki tj. TDD, No bugs, Done Done z podkresleniem, ze musisz stosowac wszystko i nie jutro, ale dzisiaj. Praktykowanie TDD nie jest latwe i wiekszosc mniej doswiadczoncyh zespolow pracujacych pod etykieta Scrum (lub bez) po prostu odklada w czasie zastosowanie tej (lub innych rownie wymagajacych) praktyki (w koncu sami sie organizujemy i skoro to dla nas trudne, mozemy to zaczac pozniej). Jak sie ma kiepskich rzemieslnikow to Scrum niewiele pomoze. To troche tak jak z nasza reprezentacja pilki noznej - czy wierzycie ze z super taktyka super trenera nasi kopacze zdobyliby puchar w 2012? Moze i zagraliby lepiej, a moze wrecz przeciwnie bo np. kluczem dla taktyki bylaby gra z pierwszej pilki, a to trzeba umiec.
Moj glowny zarzut dla tej metody to proba wmowienia, ze wytwarzanie oprogramowania moze byc efektywne i dosc latwe (ze Scrumem), z niedostateczny podkresleniem wagi rzemiosla zespolu i jego czlonkow.
Ta wypowiedź dokładnie wyraża mój pogląd na temat lekkich metodyk, które mimo swojej nazwy nie są wcale lekkimi we wdrożeniu, bo ich lekkość jest zauważalna dopiero, kiedy zespół jest doświadczony, aby pozwolić sobie na samoorganizowanie i dyscyplinę pracy. Skąd niby członkowie młodych zespołów (doświadczeniem nie wiekiem) mieliby wiedzieć, czego chcą, jeśli ich jedyną potrzebą jest skończyć projekt (nic nadzwyczajnego, normalka) w technologi, którą dopiero poznają, bo wszyscy wkoło mówili, że fajna?! Przejaskrawiam z tą nową technologią, ale traktuję go jedynie jako przykład i możnaby tutaj podstawić wszystko inne, które kojarzy nam się z fantazją młodych zespołów. Tutaj podkreślam, że zwykle w takich zespołach panuje przekonanie, że fajna technologia będzie panaceum na brak doświadczenia zespołu oraz skróci czas, tak że projekt nie tylko, że zakończy się w założonym czasie, ale i budżecie (!) Marzenia ściętej głowy, co? Powiedziałbym, że zwinny zespół można budować z osób, które samodzielnie również potrafiliby poprowadzić ten projekt tyle, że zajęłoby im to więcej czasu. Siła doświadczonych inżynierów wynika z ich pragmatycznego podejścia do tematu i świadomości, że po ciekawym poranku z kawką i eclipsową wtyczką do pudelka (ukłon w stronę Mateusza) przyjdzie im ciężko popracować przy zadaniu. Jeśli do tej ciężkiej pracy dodamy niezwykle wyrafinowany funkcjonalnie superszkielet aplikacyjny, to mamy dwa podejścia, idziemy z nim w szranki i konkury, aby go poznać, a wtedy wdrażamy, albo po prostu zarzucamy, bo z naszych obliczeń wynika, że albo on albo my. To nazywam pragmatycznym podejściem. Mamy świadomość, że nie wszystko teraz i zaraz, że niektóre superrozwiązania jeszcze na nas poczekają. Takiej świadomości nie możemy oczekiwać od niedoświadczonych członków zespołu.
A Ty jak uważasz? Pewnie nie tylko ja jestem zainteresowany Twoim zdaniem, więc podejmij temat i wyraź swoją opinię na forum w wątku [warszawa-jug] (wady) Agile - Dyskusja.
01 listopada 2009
Programowanie w Clojure - część 1 - tytułem wstępu
Jest wiele cech różniących programowanie funkcyjne od programowania obiektowego, ale podstawowym mogłyby być "czyste" funkcje (ang. pure functions), których wynik działania zależy wyłącznie od parametrów wejściowych, a nie od aktualnego stanu aplikacji czy innych ukrytych źródeł danych możliwych do wykorzystania w implementacji funkcji. Wiemy, że te same dane dają ten sam wynik bez względu na moment, w jakim funkcja została wykonana. Programowanie z czystymi funkcjami nie jest obce programistom javowym. W Javie to my jednak decydujemy, czy tworzona funkcja jest czysta (bez efektów ubocznych), czy nie. Wszystko zależy od implementacji. W programowaniu funkcyjnym może to być w ogóle niemożliwe do realizacji, albo wymaga specjalnych słów kluczowych w języku, które explicite oznaczą funkcję, jako posiadającą efekty uboczne. Możnaby powiedzieć, że w Javie też tak jest, bo pewne konstrukcje mówią nam, czy skutkują efektem ubocznym, czy nie, ale to wymaga analizy implementacji, podczas gdy w Clojure jedynie wyszukania odpowiednich słów kluczowych obejmujących sekcje modyfikujące.
Kolejną składową programowania funkcyjnego są niezmienne struktury danych (ang. immutable data structure). Każda operacja na strukturze danych powoduje jej skopiowanie i wykonanie operacji. Każdorazowo dostajemy kopię struktury wejściowej.
Łącząc obie cechy programowania funkcyjnego, efekty uboczne (ang. side effects) nie wystąpują w ogóle, albo są wyjątkiem a nie regułą. Tym samym testowanie aplikacji jest prostsze. Sprawdzamy, czy dla tego samego wejścia mamy to samo wyjście i tyle. Podobnie z programowaniem współbieżnym (równoległym). Skoro wszystkie struktury są niezmienne, a działanie funkcji zwraca ich kopię, to nie ma czego synchronizować (!) Idąc dalej, programowanie funkcyjne daje możliwość zrównoleglania obliczeń, skoro wynik działania funkcji zależy wyłącznie od danych wejściowych, a kolejność obliczeń składowych funkcji złożonej nie ma znaczenia. I dalej, jeśli wykonamy funkcję czystą z parametrami wejściowymi, które będą wykorzystane w kolejnym wywołaniu, możemy zoptymalizować takie wywołanie podstawiając wynik poprzedniego wywołania funkcji - umieścić wynik w pamięci podręcznej i kosztem funkcji będzie jedynie odczyt z pamięci.
Nie ma nic za darmo. W końcu, gdyby taka słodycz płynęła z programowania funkcyjnego, zamiast programowania imperatywno-obiektowego w Javie pisalibyśmy aplikacje w Haskellu, OCamlu, MLu, czy microsoftowym F#. Coś jest na rzeczy, że bliżej nam do imperatywnego myślenia niż funkcyjnego, podobnie jak z bazami danych, które przyzwyczailiśmy się postrzegać relacyjnie, a oprogramowywać dostęp do ich danych obiektowo (stąd powód dla rozwiązań ORM). Dobrze jednak mieć wybór. Clojure jest językiem uruchamianym na JVM z całym dobrodziejstwem inwentarza, a wprowadza nas w świat programowania funkcyjnego, w którym niektóre problemy rozwiązuje się prościej. Miejmy po prostu świadomość istnienia Clojure, podobnie jak Groovy, Scala, JRuby, Jython, Erlang, itp. i dobierajmy narzędzie do problemu, a nie komplikujmy problem, aby dopasować i rozwiązać go znanymi technikami.
Nie mam jeszcze wyrobionego zdania nt. Clojure i jego użycia w projektach. Próbuję wierząc, że komentarze i dyskusje dadzą mi odpowiedź, czy jest warto. Jedna książka to zdecydowanie za mało, aby wyrobić sobie pogląd na temat zastosowania Clojure w projektach. Skoro znalazła się osoba Rich Hickey, która stworzyła kolejny język funkcyjny Clojure, tym razem działający na JVM, to musi w tym być jakaś ukryta wiedza, której zrozumienie nie jest mi jeszcze dane. Kładę to raczej na barki mojego intelektualnego niedorozwoju niż braku sensu w powstaniu Clojure. Kiedy będę mógł porównać Javę do Clojure i wskazać zalety jednego względem drugiego, nawet jeśli skończy się na wyrzuceniu Clojure jako niepotrzebnego, mam nadzieję, że mimo wszystko nie będzie to czas stracony. Podkreślam słowo nadzieja. Aktywność na grupie dyskusyjnej użytkowników Clojure wskazuje, że język ma swoje poletko, w którym sprawdza się. Chcę wiedzieć, czy dla mnie również.
Ciekawym podsumowaniem cech programowania funkcyjnego może być prezentacja Programowanie funkcyjne - wprowdzenie p. dr inż. Marcina Szlenka z PW. W katalogu spop, gdzie znajduje się prezentacja, znajdziemy 2 zadania, które możemy spróbować zrealizować w Clojure (autor zażyczył sobie implementacji w Haskellu). Zawsze to łatwiej uczyć się nowego mając określone zadanie, poza takim ogólnym jak po prostu nauczyć się nowego :)
Clojure to Lisp na JVM. Składniowo oznacza to mnóstwo nawiasów, aczkolwiek twórca Clojure zrozumiał, że była to jedna z bolączek Lispa i w wielu miejscach, gdzie Lisp wymagałby nawiasów, w Clojure ich nie ma. I dobrze.
W książce "Programming Clojure" autor napisał:
"My personal quest for a better JVM language included significant time spent with Ruby, Python, and JavaScript, plus less intensive exploration of Scala, Groovy, and Fan. These are all good languages, and they all simplify writing code on the Java platform. But for me, Clojure stands out."
Interesujące, co? Nie potrafiłbym porównać wszystkich wymienionych języków, więc nie pozostaje mi nic innego, jak uwierzyć autorowi na słowo i samemu spróbować swoich sił z Clojure. Ciekawe jest, że obok Clojure pojawiły się również Scala, Groovy i nieznany mi w ogóle Fan. Wystarczy chwila na stronie Fan i trudno nie oprzeć się wrażeniu, że wszystko w nim jest, a jednak brakuje mu zainteresowania społeczności. Dlaczego? Działa na JVM, .Net CLR i w przeglądarce. Ma jakby wszystko, co daje Java, a środowisko szersze, a jednak czegoś brakuje. Ktoś parał się nim?
Wracając do Clojure, zacznijmy od początku. Rozpoczynamy programowanie w Clojure pobierając kompilator i środowisko uruchomieniowe ze strony domowej Clojure. Pobieramy Clojure 1.0.0 i rozpakowujemy w wybranym katalogu. Ja wybrałem trochę bardziej pokrętną drogę i zbudowałem Clojure lokalnie, więc numery wersji będą różne. Dla zainteresowanych, wystarczy
svn co http://clojure.googlecode.com/svn/trunk/ clojureUruchamiamy interpreter Clojure - REPL - poleceniem
cd clojure
ant
jlaskowski@work /cygdrive/c/oss/clojureJest to podobne do groovysh czy groovyConsole w Groovy i pewnie podobne rozwiązania znajdziemy również w Scali czy Erlangu. Składnia Clojure wymaga, aby każde polecenie było podawane w nawiasach, w których najpierw podajemy funkcję, a później jej parametry, np.
$ java -jar clojure-1.1.0-alpha-SNAPSHOT.jar
Clojure 1.1.0-alpha-SNAPSHOT
user=>
user=> (+ 1 3)Jeśli wymagane jest dalsze zagnieżdzenie struktur, mamy kolejny poziom nawiasów, itp.
4
user=> (- 3 1)
2
user=> (* 2 4)
8
user=> (+ 2 (- 5 2))Z pewnymi wyjątkami nawiasy wyznaczają zakres działania funkcji. Skoro wszystko jest funkcją, będzie tyle par nawiasów, co wykonywanych funkcji.
5
Koniec pracy w REPL to znane i lubiane Ctrl-C. To byłoby tyle na dobry początek. Zainteresowany? A czym?! :) W kolejnych wpisach dalsze podboje programowania funkcyjnego z Clojure przy tworzeniu czegoś bardziej użytecznego niż przysłowiowe "Witaj Świecie".
Subskrybuj:
Posty (Atom)