28 września 2010

Zapytany o Clojure na gtalku - Jaki on jest? Warty naszego czasu?

1 komentarzy
Coraz częściej wokół mnie pojawia się pytanie o sens nauki Clojure. Tym razem padło na gtalku, więc po odpowiedzi, postanowiłem ujawnić ją (niedługą!) na blogu pro publico bono. Komentarze mile widziane.

ON: Hej Jacku. Pozwolę sobie tak zadać jedno konkretne pytanie. Bazując na Twojej wiedzy i doświadczeniu. Clojure jest: fajne, ciężkie, przereklamowane, rewolucyjne, tego mi było trzeba, daje duże możliwości (nie potrzebne skreślić) :-)

JA: Gdybym miał wybierać z podanego zbioru, wybrałbym na pewno: fajne i rewolucyjne. Do dwóch ostatnich muszę się przekonać, ale tak jest reklamowane. Ktoś powiedział, że "jeśli język programowania nie wymaga zrewolucjonizowania myślenia, to nie warto się go uczyć". Można to przypisać do każdej z naszych działalności, nie tylko nauki nowego języka programowania, czy technologii. Dla mnie Clojure jest po prostu boski (dla wielu takim jest Ruby czy Scala, ale są one z mojego punktu widzenia jedynie ewolucyjne - Clojure jest Rewolucyjne).

ON: No może jeszcze jedno. Czy obecnie Clojure i biblioteki może być stosowane do "prawdziwych' web aplikacji? Bo mi się kojarzy to tylko i wyłącznie do bibliotek server side

JA: Zacznij od compojure, któremu się przyglądam. Możesz również skorzystać z sandbar. To są dedykowane biblioteki/rozwiązania na potrzeby aplikacji webowych. Jeśli do tego dodać, że Clojure to Java, a raczej swego rodzaju DSL do Javy (bo ostatecznie i tak mamy bajtkod i jesteśmy nim ograniczeni), to czy Clojure czy Java nie ma wielkiego znaczenia w kontekście gotowości szkieletów webowych. Każdy się nadaje. Warto jednak skorzystać z możliwości Clojure i nie łatać, a łączyć, czyli...compojure albo sandbar.

ON: Hmm, w zasadzie skoro Clojure operuje na JVM to po co dedykowany web framework do niego? Czy nie można po prostu skorzystać z HTML, JS i w jakiś sposób (mam nadzieję, ze jakoś się da) wystawić REST serwisy? W zasadzie powinno dać się mieszać Clojure ze Scalą i Groovy :-) 

JA: Nie czuję się w pełni przygotowany do odpowiedzi na to pytanie, bo pewnie, gdybym znał odpowiedź, stworzyłbym jej realizację - nowy szkielet webowy bazując na dotychczasowych doświadczeniach w JEE. Z mojej perspektywy funkcyjnej wygląda jednak, że celem dowolnego "web framework" jest uproszczenie tworzenia aplikacji i konstrukcje języka mogą w tym bardzo pomóc, np. w Clojure mamy makra, które operują na danych podczas kompilacji, zamiast funkcjach, które operują podczas uruchomienia. Łącząc obie konstrukcje języka możemy stworzyć rozwiązanie - szkielet webowy, w którym zadeklarowanie obsługi żądania sprowadzi się do 2-3 zrozumiałych linii zamiast 20-30 niekoniecznie zrozumiałych (chociażby przez swoją objętość). W końcu, w programowaniu funkcyjnym nie chodzi o to jak, ale co.

Problem z dotychczasowymi szkieletami webowymi to, że bierzesz wszystko, albo masz kłopoty. Trudno skorzystać z części i wyskoczenie poza ramy może być bolesne, a już połączenie z innymi rozwiązaniami może przyprawić o ból głowy. Stąd też różnica między rozwiązaniami typu szkielet (framework) a rusztowanie (scaffolding). Pierwsze zostanie na wieki, drugie wyrzucasz, kiedy nabierzesz wprawy i doświadczenia, albo kiedy zorientujesz się, że są prostsze rozwiązania.

W Clojure forowane podejście polega na łączeniu mniejszych cegiełek do zrealizowania tworzenia aplikacji webowych. Tu nie ma pytania: Seam vs Struts2 vs Wicket vs JSF, ale składa się rozwiązania, aby obsłużyć bardziej skomplikowany problem - w końcu wszystko w Clojure jest funkcją, albo idąc dalej, strukturą danych, np. listą. Jeśli składanie funkcji matematycznych jest powszechnie stosowane i zrozumiane, to dlaczego tworzenie oprogramowania takim nie może być?!

Odnośnie REST Web Services i łączenia ze Scalą i Groovy, uchylam się od odpowiedzi. Za mało mam jeszcze zrozumienia, gdzie i jak mógłbym zastosować Clojure, aby łączyć go z Javą i wykorzystania z JEE, a gdzie tu jeszcze dokładać do tego Scalę i Groovy.

ON:...dopiero do niego wysłałem odpowiedź, więc ciąg dalszy nastąpi...kiedyś.

24 września 2010

Teoretycznie długo o monadach (Clojure gdzieś w tle)

2 komentarzy
Trochę trwało zanim poczułem się w stanie chociażby nakreślić, czym są monady i przestrzegam, że ich zrozumienie opieram na własnych lekturach bez możliwości ich skonfrontowania z istotą żywą. Zabrałem się za ten temat przez pryzmat teorii teorii kategorii (podwojenie 'teorii' zamierzone), czyli matematycznie oraz przez pryzmat zastosowań programistycznych, czyli informatycznie. W żaden sposób nie twierdzę, że zawarte w tekście tezy są w jaki sposób właściwe dla zrozumienia pojęcia monad, a mogą wręcz doprowdzić do aktów samobójczych znawców tematu :) Niech mi to wybaczą.

Zabrałem się za temat zrozumienia monad przez serię artykułów, które zebrałem w serwisie delicious w kategorii (tym razem nie w dosłownym sensie matematycznym, a jedynie jako zgrupowanie) monads. Tam też można znaleźć mój pogląd, na każdy z przeczytanych artykułów i tam też będę odkładał kolejne.

Zrozumienie działania monad chciałbym wykorzystać do wzbogacenia swojego warsztatu programistycznego w narzędzia wykorzystywane w programowaniu funkcyjnym z praktyczną realizacją z użyciem języka Clojure. Jest wirtualna maszyna Javy z całym jej dobrodziejstwem inwentarza, jest język funkcyjny, więc nic więcej mi nie trzeba. Bazując na znanym (JVM i Java) poznaję nieznane (programowanie funkcyjne z Clojure). Tyle się mówi o poznawaniu innych paradygmatów programowania, aby wzbogacić swój warsztat, więc postawiłem na zrozumienie programowania funkcyjnego i monady wydały mi się wystarczającym wyzwaniem.

Poza w/w artykułami na delicious, dzięki uprzejmości Michała Januszewskiego, miałem przyjemność przeczytać 3 rozdziały o monadach w wydaniu Haskella w książce Real World Haskell wydawnictwa O'Reilly, które nie pomogło jednak wiele, bo przykłady były w języku, którego wcale nie znam. Dodatkowo, w przeciwieństwie do Clojure, Haskell ma leniwe (ja jednak wolę określać to jako opóźnione) wykonywanie funkcji (a może nawet w ogóle nie dojść do ich wykonania!) oraz wyłącznie funkcje czyste (bez efektów ubocznych, gdzie jedynym środowiskiem działania funkcji są dane wejściowe, a wynik przekazywany w postaci zwracanej struktury). To znaczna różnica nie tylko między Javą a Clojure czy Haskell, ale nawet między reprezentantami tej samej kategorii programowania funkcyjnego - Clojure vs Haskell.

Najbardziej przysłużyło mi się nagranie Introduction to Monads - Adam Smyczek. Przesłuchałem nagrania bodaj 10 razy do 20 minuty, z czego trzykrotnie doszedłem do samego końca, co okazało się być już jednak niewielkiej wartości merytorycznej, bo właśnie pierwsze 20 minut jest kluczowe (jakimś cudem po tych 20 minutach...zasypiałem :)). Kiedy tak wsłuchiwałem się w każde słowo wypowiadane przez niego i porównywałem z już znaną mi teorią, pojęcie monady stawało się bardziej zrozumiałe. Dzisiaj kolejny raz zajrzałem do Wikipedii, w której pod pojęciem Monad (functional programming) znajduje się wyjaśnienie informatyczne monady. Mogę powiedzieć, że wysiłki ostatniego miesiąca w obszarze monad zakończyły się co najmniej zrozumieniem wstępu na Wikipedii. Mimo swojej niewielkiej ilości pod względem treści, twierdzę, że można nazwać to niewielkim, acz znaczącym sukcesem (nawet, jeśli trwało to miesiąc i po drodze nic nie szło, co możnaby nazwać porażkami, teraz suma porażek staje się sukcesem :)). I teraz, wyjaśnienie na Wikipedii ma sens i jest faktycznie kwintesencją wprowadzenia do monad dla programistów (ale tylko tych, którym dane jest myśleć poza ramami aktualnie wykorzystywanego języka programowania, co dla wielu wśród moich czytelników sprowdzi się do Javy - obym się mylił!).

Gdyby zacząć wprowadzenie do monad matematycznie, to należałoby wskazać na dobre wprowadzenie do teorii kategorii na MIMUWowym ważniaku - Teoria kategorii dla informatyków. Proponuję zacząć od wykładu Wykład 1: Teoria kategorii jako abstrakcyjna teoria funkcji, aby po nim przejść do Wykład 11: Monady. Ech, gdyby jeszcze opublikowano nagrania z wykładów, byłoby cudnie i znacznie uprościłoby zrozumienie informatyki teoretycznej (co wierzę, że miałoby ostatecznie przełożenie na nasze projekty).

Zatem, czym są monady i czy jest się czym ekscytować?

Założenia naszego rozumowania są takie, że funkcja nie ma skutków ubocznych (całe jej środowisko jest jej przekazywane w postaci parametrów wejściowych) oraz nie zawsze musi być wykonana w miejscu jej wystąpienia w kodzie źródłowym (bo i po co, kiedy nikt nie oczekuje na zwracany przez nią wynik). To może się nie mieścić w głowie osobom z praktycznym programowaniem imperatywnym, a szczególnie obiektowym, w którym o skutki uboczne się właśnie gra. Nie raz, nie dwa, działanie funkcji to zmiana parametrów wejściowych przekazywanych przez referencję, a nie wartość, więc poza zwracanymi wartościami może dojść do zmiany przekazywanych. Innymi słowy, wielokrotne wywołanie danej funkcji z tymi samymi parametrami nie musi zwracać tego samego wyniku, mimo, że pamiętamy z lekcji matematyki, że funkcja dla danej wartości zawsze tak robi i dla tych samych danych wejściowych zwraca tę samą wartość. Zysk z takiego założenia jest prosty - można zapamiętać wynik długotrwającej funkcji i raz ją wykonując dla pewnych danych wejściowych zapomnieć o niej dla nich. Wtedy i zrównoleglanie obliczeń jest łatwiejsze, bo nie trzeba niczego synchronizować, albo niewiele. I nie piszę o wsparciu przez programistę, ale przez samo środowisko uruchomieniowe!

Przy takich założeniach można postawić sobie pytanie, jak w czystym i leniwym języku można zrealizować funkcjonalność wypisywania na ekran, albo przechowywania stanu, coś na wzór stanu globalnego programu, kiedy i ekran i stan są zabronione w programowaniu funkcyjnym? Wszystko za sprawą ich nieczystości - ekran może w danej chwili prezentować coś innego niż chwilę wcześniej, więc jako funkcja jest nieczysta. Idąc tym tropem, można właśnie potraktować ekran jako funkcję, której przekazuje się, co ma być wyświetlane. To mi przypomina rekurencję za pomocą akumulatora, gdzie zapobiega się zbyt zagnieżdżonym wywołaniom przez odnotowanie aktualnego wyniku w akumulatorze i zamianę (zamiast odłożenia) aktualnego wywołania na stosie wykonywania programu.

Matematycznie monada jest trójką z funktorem i dwoma morfizmami, które spełniają pewne założenia (patrz Definicja 11.1 [monada] w Wykład 11: Monady). To wciąż dla mnie niewiele mówiąca definicja, aczkolwiek jeśli się nie mylę, chodzi o takie przekształcenie obiektu (w sensie teorii kategorii nie programowania obiektowego) na drugi (przez morfizmy), aby nie zaburzyć jej struktury, tj. praw w nim zachodzących. Innymi słowy, mamy obiekt i wzbogacamy go, ale jego właściwości się nie zmieniają (tutaj nie jestem tego dokładnie pewien, czy dobrze się wyrażam - wybaczcie puryści językowi).

Teoretyczna informatyka przedstawia monady jako...tak, tak...trójkę składającą się z abstrakcyjnego typu opisującego akcje/działania (zamiast danych jak w warstwie danych w modelu obiektowym) oraz dwóch funkcji m-return i m-bind (w notacji Clojure). Pierwsza z nich - m-return - służy do wstrzykiwania/opakowania typu wejściowego do typu monadycznego, a m-bind rozpakowuje typ monadyczny i przekazuje wynik akcji do kolejnej (wciąż będąc w ramach przestrzeni obiektów wzbogaconych). W ten sposób możemy dostarczać dodatkowych funkcjonalności do już istniejących funkcji bez wpływu na ich implementację. Widzę tutaj analogię do interceptorów czy dekoratorów, aczkolwiek pamiętam, że wielu przestrzegało mnie przed takim (imperatywnym!) myśleniem. Cóż, na razie po prostu widzę ich analogie - przywołując pierwsze zdanie we wspomnianym już wykładzie Teoria kategorii dla informatyków "dobry matematyk to taki, który potrafi znaleźć analogie pomiędzy twierdzeniami, zaś wybitny matematyk to ten, który widzi analogie pomiędzy analogiami". Niestety nie należę do żadnej z tych kategorii :]

Jeśli wyobrazimy sobie stan zewnętrzny jako dodatkowy i ukryty parametr przekazywany między funkcjami to stan programu może być odczytany w każdej chwili właśnie z owej ukrytej struktury. Czyż nie przypomina nam to w Javie dodatkowego parametru this przekazywanego do każdej funkcji instancji (niestatycznej)?! Mam wrażenie, że to bardzo podobny mechanizm, gdzie programista rozpoczynający swoje życie programistyczne w Javie wie, że ma this, ale nie wie dlaczego. Jeszcze kolejny przykład z naszego podwórka javowego, aczkolwiek niewiele mającym związku z monadami, ale z ukrywaniem informacji już tak - generowanie domyślnego, bezparametrowego konstruktora. To też nam jest dane, bo programujemy w Javie. Język programowania to środowisko, w którym poruszamy się, aby wyrazić/zamodelować pewne czynności z życia realnego i Clojure, czy programowania funkcyjne w ogólności, możemy porównać do tego. Monady są pewną konstrukcją programistyczną, którą wielu nazywa kontenerem, jak kontener EJB w serwerze aplikacyjnym. Jeśli dopasujemy swoje modelowanie do zasad rządzących w kontenerze będzie łatwiej, ale najpierw musimy zrozumieć jego zachowanie, co już łatwym zwykle nie jest. Podobnie z monadami. To kontener, gdzie mając 2 obowiązkowe funkcje m-return i m-bind z naszą implementacją wpasowujemy się w kontrakt monadyczny. Jeśli tylko tak się stanie, możemy wyczyniać znacznie więcej, mniejszym kosztem (długofalowo, bo moje miesięczne boje wcale o tym nie świadczą). To jest cel mojej podróży w świat monad i programowania funkcyjnego - dobra zabawa, która ostatecznie ma przynieść korzyści mentalne (a jeśli jeszcze i finansowe, to tym lepiej).

Dla wytrwałych, dodatkowa porcja wiedzy nt. monad z przeczytanych artykułów.

Monada jest reprezentowana przez strukturę danych, która z kolei reprezentuje wynik działania lub samo działanie. Strukturę danych będącą podwaliną monady nazywamy monadyczną strukturą danych. W językach czystofunkcyjnych jak Haskell lub mniej restrykcyjnych jak Clojure, monady wspierają tworzenie aplikacji z jawnie zmiennym stanem, którego zmiana jest jednak niemożliwa (symuluje się ją przez kopiowanie struktur). Sam algorytm mógłby wskazywać na zachodzące zmiany w programie, a dzięki monadom są one symulowane (zaczynam przedstawiać monady jak inni, jakimś bliżej niezrozumiałym językiem - przyznaję, że sam nie wiem, o czym jeszcze piszę i wychodzi jakiś bełkot :))

Monada (chociażby w Clojure) opisana jest przez 2 obowiązkowe funkcje - m-result i m-bind oraz 2 dodatkowe - m-zero i m-plus.

m-result zamienia dowolną wartość na jej reprezentację monadyczną, wyrażoną konstrukcjami owej struktury monadycznej.
m-bind przekazuje wartość monadyczną (wyrażoną strukturą monadyczną) do funkcji jednoargumentowej, która wykonuje obliczenia (po co, wyjaśni się w przykładach, które kiedyś się w końcu pojawią - przypominam, że wciąż zgłębiam tę tajemną wiedzę i liczę na wyrozumiałość z niemałą dawką cierpliwości).

Innymi, bardziej matematycznymi słowy, acz wciąż jedynie bardzo delikarnie, trójka określa monadę.

Nieobowiązkowe funkcje to:
- m-zero reprezentująca specjalną wartość monadyczną, która odpowiada działaniu bez wyniku, np. zakończonego wyjątkiem lub po prostu zwróceniem nil.
- m-plus jest złożeniem wyników dwóch lub więcej obliczeń/działań.

Zwykle istnienie jednej implikuje (prowokuje?) istnienie drugiej, ale nie jest to obowiązkowe.

Wynika stąd, że m-result, m-bind, m-zero i m-plus operują na pewnej strukturze danych, którą wszystkie muszą respektować.

W kolejnej odsłonie zaplanowałem sobie bardziej praktyczne przedstawienie monad, przez przykłady.

20 września 2010

Compojure w akcji - trasowanie i obsługa parametrów

0 komentarzy
W artykule Clojure w aplikacji webowej z Compojure oraz Ring i Hiccup przedstawiłem szkielet aplikacji webowych Compojure zbudowany na języku Clojure. Compojure jest w tak aktywnym rozwoju, że mimo wersji 0.4.1 już dorobił się kilku bibliotek wspierających - hiccup oraz clout, a sam jest nakładką dla innej biblioteki ring. Nie stanowią one bardzo obszernych rozwiązań - szkieletów aplikacyjnych, do których przyzwyczaiłem się w Java EE i zrozumienie sensu istnienia wspomnianych stanowi dla mnie nie lada wyzwanie. A gdzie tu jeszcze umiejętne użycie.

W tym odcinku-artykule w serii o roboczej nazwie "Poznajemy Clojure praktycznie" przedstawię sposób, w jaki Compojure obsługuje trasowanie (wyznaczanie tras, tj. routing) oraz parametry z formularzy (aczkolwiek w tym odcinku formularza nie będzie i będzie jedynie przekazywanie parametrów przez adres URL).

Zainteresowanych zapraszam do lektury artykułu Compojure w akcji - trasowanie i obsługa parametrów.

Jest to kolejny krok ku zrozumieniu narzędzi budowania aplikacji webowych w Clojure i praktycznego jego wykorzystania - budowania aplikacji webowych (co oczywiście nie oznacza, że jest to dobrze dobrane narzędzie do problemu). Siła Clojure płynie przede wszystkim z użycia makr, które tłumaczone są na właściwe wywołania funkcji (w postaci bajtkodu) podczas kompilacji. Clojure nie jest językiem interpretowanym, więc tym samym Compojure staje się DSLem do tworzenia aplikacji webowych. DSL w założeniu ma być językiem dziedzinowym, a skoro mowa o aplikacjach webowych, to dobrze jest korzystać z języka, które takie aplikacje wspiera wprost. Java zdecydowanie takim językiem nie jest - jest zbyt ogólna i możnaby powiedzieć, że jest zbyt niskopoziomowa, podobnie jak C czy assembler.

17 września 2010

Clojure w aplikacji webowej z Compojure oraz Ring i Hiccup

0 komentarzy
Logo ClojureMiało być filmowo, a wyszło literacko - zamiast skrinkastu pojawił się artykuł Clojure w aplikacji webowej z Compojure oraz Ring i Hiccup, w którym (cytując samego siebie z artykułu) zaprezentuję alternatywne podejście do tworzenia aplikacji webowych z Clojure i rozwiązaniami wspierającymi opartymi na nim - Compojure, Ring oraz Hiccup. W skrócie, Compojure to szkielet webowy, Ring to mechanizm uruchamiający środowisko kontenera webowego (w naszym przypadku będzie to Jetty) oraz Hiccup do tworzenia szablonów stron webowych. Po więcej informacji zapraszam na odpowiednie strony domowe projektów (które nota bene są we wczesnej fazie rozwojowej i większość z nich to projekty na GitHubie). Swoje miejsce znalazły również Leiningen, NetBeans IDE i enclojure!

Chciałbym tym samym podziękować Danielowi Janusowi, który zapoczątkował moje poznawanie Compojure i który niestrudzenie pomaga mi zrozumieć tajniki samego Clojure w postaci komentarzy na tym blogu. Jestem zobowiązany. Dzięki.

Gdyby tego było mało, właśnie pojawiła się inicjatywa poprowadzenia zainteresowanych poznaniem Clojure przez zakamarki języka i rozwiązań wspierających w postaci nowego bloga Clojure Blog. Na mój apel odnośnie pomocy w prowadzeniu takowego, napisał do mnie Marcin Rzewucki i tak oto, po kilku dniach, mamy owoc jego pracy. Nie daj mu zapomnieć o swoim krytycznym (w pozytywnym tego słowa znaczeniu) podejściu do publikowanych tam treści i daj się zauważyć, np. w postaci komentarza, albo pomysłu na kolejne wpisy. Marcin wykazuje niespożytą energię, którą zamierza przeznaczyć na przybliżanie nam Clojure, ale świat jest pełen pokus i może zdryfować :-)

Wracając do artykułu...Co jest niezwykle odświeżającym doświadczeniem, które pozostawia niezapomniane wrażenia po przeczytaniu tego artykułu, to fakt, że stworzenie aplikacji webowej w Clojure nie wymaga bardzo rozległej wiedzy. Szkielety webowe w Clojure składa się z interesujących i potrzebnych funkcjonalności, co stoi w przeciwieństwie do bardzo rozbudowanych (i wielokrotnie "spłycanych" do wykorzystania nielicznych funkcjonalności) szkieletów javowych. To może być cenione i ganione jednocześnie. Dla mnie, początkowo, taki sposób budowania aplikacji webowych przypominał programowanie w assemblerze, ale nie trwało długo, aż zdałem sobie sprawę, że może doświadczam niepotrzebnego zwątpienia, bo być może tylko tyle mi akurat potrzeba i szczęśliwie dobrałem właściwe narzędzie do problemu (a nie próbowałem dopasowywać problem do narzędzi). Ocenę pozostawiam Tobie.

Zapraszam do lektury Clojure w aplikacji webowej z Compojure oraz Ring i Hiccup. Nie powinien zająć więcej niż 10 minut. Uwagi i komentarze mile widziane, a wręcz oczekiwane.

14 września 2010

Alfabet w Clojure - zagadka z kanału @learnclojure

4 komentarzy
Logo ClojureDzisiaj będzie niezwykle krótko, ale bardzo intensywnie mentalnie. Nauka Clojure sprawia, że coraz bardziej "żałuję", że się za ten język zabrałem - coraz więcej problemów przypominających zagadki algorytmiczne, w których nigdy nie byłem orłem (albo nie odkryłem w sobie powołania), albo wręcz zejście na tematy bardzo abstrakcyjne matematycznie. Możnaby rzec, że programowania "produkcyjnego" zostało niewiele (zresztą nigdy w moim wykonaniu nie było), a "teoretyczne" wyraźnie zwyżkuje.

Od dzisiejszego wieczoru, kanał @learnclojure na twitterze przeszedł pod opiekę autora książki "The Joy of Clojure". Książka niebawem pojawi się w sprzedaży, a dzięki uprzejmości wydawnictwa Manning, mam ją przyjemność czytać od kilku tygodni i szczerze cierpię z powodu nadmiaru informacji, których nie mogę w żaden sposób strawić w tempie, jakim bym sobie życzył - powiedzmy, zamiast tradycyjnego 5 x 45 minut tygodniowo nauki Clojure (jak to miało miejsce za czasów szkolnych z chociażby matematyką), zamarzyło mi się podwoić to. I boli, bo zintensyfikowanie wysiłków przy nauce Clojure (przez m.in. zwiększenie czasu dla niego), wcale nie przekłada się na zwiększenie przyswajania wiedzy o nim w strawnej postaci. Czuję, że zaczynam wykorzystywać te miejsca w moim umyśle, które do tej pory były rezerwowane na pogrywanie sobie w Civilization, albo Traviana :-)

Jeśli ktoś nie wierzy, że nauka języka programowania może boleć i chciałby poczuć klimaty, w których przychodzi zmagać się z "oczywistościami" dla osób, które pojęły już te tajniki (jak ma miejsce często przy rozmowach o Javie w moim przypadku, kiedy to nie mogę zrozumieć, że Java może być i jest dla wielu trudna), niech przyjrzy się poniższej konstrukcji (zaczerpnięte z dzisiejszego statusu na kanale @learnclojure) i odpowie na pytanie: "Co będzie wynikiem wykonania?".
user=> (->> (iterate inc (int \a)) (take 26) (map char))
;...jaki będzie wynik?
user=> (map char (range (int \a) (int \{)))
;...wynik jak wyżej i pytanie to samo
Szacunek dla tego, komu tego typu cudeńka nie sprawiają problemu. Ja, kiedykolwiek widzę podobne konstrukcje, zwijam się z bólu, a głowa eksploduje. Pora trawić.

W ramach uzupełnienia, trochę dokumentacji, która może pomóc w zrozumieniu powyższego.
user=> (doc ->>)
-------------------------
clojure.core/->>
([x form] [x form & more])
Macro
  Threads the expr through the forms. Inserts x as the
  last 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
  last item in second form, etc.
nil
user=> (doc iterate)
-------------------------
clojure.core/iterate
([f x])
  Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free of side-effects
nil
user=> (doc inc)
-------------------------
clojure.core/inc
([x])
  Returns a number one greater than num.
nil
user=> (doc int)
-------------------------
clojure.core/int
([x])
  Coerce to int
nil
user=> (doc take)
-------------------------
clojure.core/take
([n coll])
  Returns a lazy sequence of the first n items in coll, or all items if
  there are fewer than n.
nil
user=> (doc map)
-------------------------
clojure.core/map
([f coll] [f c1 c2] [f c1 c2 c3] [f c1 c2 c3 & colls])
  Returns a lazy sequence consisting of the result of applying f to the
  set of first items of each coll, followed by applying f to the set
  of second items in each coll, until any one of the colls is
  exhausted.  Any remaining items in other colls are ignored. Function
  f should accept number-of-colls arguments.
nil
user=> (doc char)
-------------------------
clojure.core/char
([x])
  Coerce to char
nil
user=> (doc range)
-------------------------
clojure.core/range
([] [end] [start end] [start end step])
  Returns a lazy seq of nums from start (inclusive) to end
  (exclusive), by step, where start defaults to 0, step to 1, and end
  to infinity.
nil

09 września 2010

Zagadka z Clojure - funkcyjna niezmienność struktur danych

5 komentarzy
Logo ClojureKontynuuję moją przygodę z Clojure i zgodnie z planem wczytuję się w różnego rodzaju publikacje na ten temat - grupa dyskusyjna clojure na Google Groups monitorowana na bieżąco, na swoim koncie na twitterze podłączyłem się do co bardziej znaczących osób w światku Clojure i się wdrażam. Stopniowo i bez szaleństw, ale konsekwentnie. Ach i bym zapomniał - książkę "The Joy of Clojure" również czytam. Trochę wolniej niż zwykle - 1 rozdział na dzień podczas, gdy zwykle przychodzi mi przejść przez kilka rozdziałów, ale biorąc pod uwagę inne aktywności wokół Clojure, to i tak uważam, że utrzymuję dosyć wysokie tempo (wysoka samoocena wie, kiedy się ujawnić).

W trakcie lektury A monad tutorial for Clojure programmers (part 3) o monadach trafiłem na przykład, w którym zamiast idiomatycznego ('ma' w tym słowie ma niebagatelne znaczenie! :)) pobrania wartości dla danego klucza w mapie (klucz mapa) zastosowano (prawie) równoważną funkcję (get mapa klucz).
user=> (doc get)
-------------------------
clojure.core/get
([map key] [map key not-found])
  Returns the value mapped to key, not-found or nil if key not present.
nil
user=> (def m {:a \a})
#'user/m
user=> (class m)
clojure.lang.PersistentArrayMap
user=> (:a m)
\a
user=> (get m :a)
\a
user=> (= (get m :a) (:a m))
true
Różnica jest niewielka, acz istotna - obsługa sytuacji braku istnienia klucza w mapie. Warto pamiętać, że różnica istnieje, ale w przywołanym przykładzie użycie funkcji (get) sprowadza się do idiomatycznego użycia klucza do poszukiwania jego wartości w mapie. Innymi słowy, klucz w Clojure jest jednocześnie funkcją, która wyszukuje wartość dla niego samego. W przypadku (get) jest możliwość podania trzeciego parametru, który zostanie zwrócony, jeśli nie zostanie odszukany klucz.
user=> m
{:a \a}
user=> (:b m)
nil
user=> (get m :b \b)
\b
Jasne? Na pewno! I nie przyjmuję odpowiedzi, że nie :]

Tak przy okazji, właśnie znalazłem przykład do zobrazowania monady, która zareaguje na sytuację braku klucza! Poszukiwałem go przez dobrych kilka dni i właśnie znalazłem. Kolejny istotny powód dla publikowania swoich doświadczeń na blogu. Właśnie, gdyby ktoś rozważał bloga, ale brakowało mu inspiracji lub przewodnika, zgłaszam się na ochotnika, aby wskazać zainteresowanemu tematy. Najlepiej niech to będzie zapaleniec programowania funkcyjnego w dowolnej postaci - Haskell, Scala, Python, Lisp, Ruby, itp.

Wracając do tematu dzisiejszego wpisu, mam dla Ciebie zagadkę związaną z Clojure, aczkolwiek więcej tu logicznego myślenia niż samej znajomości języka. Warto jednak pamiętać, że wyróżnikiem programowania funkcyjnego jest przede wszystkim czystość funkcji - im większa tym lepiej. Zero skutków ubocznych i funkcja jedynie operuje na danych wejściowych zawsze zwracając pewną wartość, w szczególności nil. Pamiętaj, że do rozwiązania zagadki potrzebne jest zapamiętanie, że...patrz na tytuł wpisu. Pora na zagadkę, która mnie osobiście na zauważalny moment "zawiesiła": Dlaczego ostatnia linia zwróci "Nie ma elementu?!"? Nagrodą jest doświadczenie błogiego stanu nirwany zrozumienia podstawowej cechy programowania funkcyjnego.
user=> (def m {:a \a})
#'user/m
user=> m
{:a \a}
user=> (get :a a)
nil
user=> (get a :a)
\a
user=> (:b m)
nil
user=> (get m :b)
nil
user=> (get m :b "Nie ma elementu?!")
"Nie ma elementu?!"
user=> (doc assoc)
-------------------------
clojure.core/assoc
([map key val] [map key val & kvs])
  assoc[iate]. When applied to a map, returns a new map of the
    same (hashed/sorted) type, that contains the mapping of key(s) to
    val(s). When applied to a vector, returns a new vector that
    contains val at index. Note - index must be <= (count vector).
nil
user=> (assoc m :b \b)
{:b \b, :a \a}
user=> (get m :b "Nie ma elementu?!")
"Nie ma elementu?!"
Dlaczego ostatnia linia zwróci "Nie ma elementu?!"? Odpowiedzi dozwolone w komentarzach. Kto pierwszy, ten lepszy.

p.s. Jeśli komuś przypadła zagadka do gustu, proszony jest o wyrażenie swojego zachwytu w komentarzu, abym wiedział, czy kontynuować tego typu wpisy.

08 września 2010

PIT-36 dla dochodów z zagranicy - zadowolony z rozmowy z US Ursynów

12 komentarzy
Dzisiejszy ranek postanowiłem przeznaczyć na małe dochodzenie odnośnie przychodów z zagranicy i ich rozliczania przez osoby nieprowadzące działalności gospodarczej. Nie pierwszy raz przychodzi mi dotknąć tego tematu i nigdy nie wiem, jak należałoby wystawić fakturę/rachunek (ang. invoice) za moją potencjalną działalność na rzecz firmy amerykańskiej.

Trzeba było widzieć moją minę, kiedy po kwadransie byłem niezwykle usatysfakcjonowany z rozmowy z Urzędem Skarbowym Warszawa Ursynów, ul. Wynalazek 3, 02-677 Warszawa.

Zaczęło się od telefonu na centralę 22 548-68-00, gdzie wybrałem tonowo 1 (mieszkaniec Ursynowa), a później 9 (operator, wcześniejsze brzmiały dosyć egzotycznie). I zaczyna się przedstawienie sprawy - firma amerykańska płaci obywatelowi polskiemu, który nie prowadzi działalności gospodarczej (aż trudno uwierzyć, że potrafiłem złożyć takie zdanie), a chciałby zgodnie z polskim prawem gospodarczym rozliczyć się jak na praworządnego obywatela przystało (końcówkę zmieniłem na potrzeby tego wpisu, bo aż tak wyniośle to nie artykułowałem sprawy). Pan przekierował mnie na Dział Rachunkowości pod numer 5486894, gdzie dowiedziałem się (po przedstawieniu sprawy), że powinienem "udać się" do Działu Podatków - i tu pojawiają się 4 możliwe numery: 5486712, 5486717, 5486708, 5486709. Zaczynam od pierwszego i po minucie rezygnuję. Drugi to samo, aby powiodło się z trzecim - 5486708. Tam dowiaduję się o kolejnym numerze 5486711. Super, dzwonię pod niego, aby dowiedzieć się, że faktura to nie sprawa dla mnie, a co najwyżej rachunek (co wydawało mi się, że jest po prostu fakturą uproszczoną, czy nieVATowską) i warto dowiedzieć się szczegółów pod kolejnym numerem 5486833 - Dział Podatku Dochodowego. Dzwonię, a tam kolejne przekierowanie na numery 5486821 lub -856 - Dział Podatku Dochodowego od Osób Fizycznych Nieprowadzących Działalności Gospodarczej. Odbiera pierwszy, aby miła pani poinformowała mnie (po konsultacjach w tle z innymi osobami, co przywołało mi na myśl, że osoba, z którą rozmawiam, to jedynie pośrednik - z naszego "proxy", albo "interceptor" z zerową implementacją poza wywołaniem docelowej metody :)), że rozwiązaniem jest PIT-36, gdzie wypisuję wszystkie dochody zagraniczne.

Trwało pewnie z kwadrans, wybrałem 10 numerów, ale cel osiągnięty. Jestem nad wyraz usatysfakcjonowany. Teraz wystarczy wystawić rachunek z wymogami płacącego i będzie cacy.

p.s. Zastanawiam się, czy byłoby łatwiej/prościej/przyjemniej, gdybym realizował to jako jednoosobowa działalność gospodarcza? Byłbym wdzięczny za wszelkie (p)odpowiedzi.

06 września 2010

Rozpoznawanie Clojure - monady i teoria kategorii

1 komentarzy
Logo ClojureWłaśnie dotarło do mnie, że ostatnimi czasy nie ma dnia, abym nie ślęczał nad jakimś materiałem związanym z Clojure - czy to czytanie książek (tym razem The Joy of Clojure z Manning), czy lektura dyskusji na grupie użytkowników Clojure, czy też artykuły (które odkładam z komentarzem na delicious i często ślę na twittera), czy w końcu analiza kodów źródłowych Clojure i próbowanie mojej wiedzy w Clojure REPL. Tym samym, całkowicie przygasiłem działalność wokół Javy i Java EE na rzecz Clojure i programowania funkcyjnego w ogólności. Nie oznacza to, że JEE poszło do lamusa, a programowanie obiektowe zarzuciłem całkowicie, tylko tym razem widzę sens dokończenia sprawy do samego końca - kiedy już przyszło mi zebrać się w sobie na poznanie programowania funkcyjnego w wykonaniu Clojure, to niech mam poczucie pełnego (samo)zrealizowania się. Wygląda na to, że cały zgiełk nowości o Java EE, który wydawał mi się nie do ogarnięcia, teraz jakby przycichł i jeśli o zgiełku informacyjnym można wspomnieć, to jego źródłem jest Clojure. Na razie nie żałuję i wydaje się być doskonałym wyborem jako baza wypadowa do kolejnych języków, które zaplanowałem rozpracować - Jython, JavaScript, JRuby i Scala (dokładnie w tej kolejności, bo nie tylko, że wskazuje się ich rodowód funkcyjny, a jeszcze przydają się w pobocznych działalnościach - Jython to główny język do skryptowania czynności administracyjnych przy IBM WebSphere Application Server, a na poznaniu tego zależy mi bardzo, aby coś, co teraz zabiera kilkugodzinny wyjazd, sprowadziło się do "kilkuminutowego" skryptu, który może również posłużyć jako dokumentacja moich administracyjnych poczynań u klienta).

W jakiś przedziwny sposób wzięło mnie na rozpoznawanie monad. Od kilku dni można zauważyć na moim koncie na twitterze (tym samym na LinkedIn i Facebooku) cały strumień o nich. Zbieram się w sobie, aby ostatecznie zebrać je wszystkie w postaci serii wpisów z przykładami, ale na chwilę obecną jedyne czym mogę się pochwalić to cały zestaw artykułów o nich. Z każdym artykułem przychodzą kolejne i zamiast maleć, wciąż mam otwartych kilkanaście zakładek o monadach. Staram się czytać jedynie te, które poruszają ich realizację w Clojure, ale zdarzają się też te z Haskellem w tle i ostatni...z teorią kategorii - A crash "Monads For Programmers" course (!) Przypomniały mi się od razu nużące lata studiowania matematyki na UMK, z których właśnie zajęcia z algebry uniwersalnej były nad wyraz interesujące (nie mogłem niestety potwierdzić tego faktu ocenami w indeksie). Był też rachunek termów i lambda z ówczesnym dziekanem. Tego typu abstrakcja wchodziła mi znacznie łatwiej niż analiza matematyczna z rachunkami różniczkowymi i niezapomnianymi dyfeomorfizmami, czy rachunek prawdopodobieństwa. Było jeszcze kilka innych zajęć, które nie trawiłem, aby teraz po latach zauważać ich potrzebę do zrozumienia konstrukcji programistycznych. Czasami myślę sobie, że studia są zbyt wcześnie i zdecydowanie za teoretyczne (bez praktycznego przełożenia). Na szczęście teoria kategorii była mi wkładana w stosunkowo przyjemny sposób i teraz cieszę się, że programowanie funkcyjne to raczej algebra niż analiza :) Ważniak na MIMUWie okazuje się kolejny raz skarbnicą wiedzy nt. monad i teorii kategorii w ogólności - Teoria kategorii dla informatyków. Po tytule wnioskuję, że nie ominie mnie przyjemność zapoznania się z tym wykładem.

Kiedy czytałem o monadach, to na myśl przychodziły mi najpierw aspekty czy interceptory, aby ostatecznie skończyć na skojarzeniu ich z dekoratorami. Widząc tak opornego (mentalnie) ucznia możnaby postawić pytanie, czy łatwiej zrozumieć programowanie obiektowe po funkcyjnym, czy odwrotnie. U siebie zauważam powolne acz rosnące zrozumienie programowania funkcyjnego, a najlepiej mi idzie, kiedy całkowicie pozbywam się myślenia obiektowo-imperatywnego. Pewnie doświadcza to każdy próbujący zrozumieć Clojure czy Scalę, a śmiem twierdzić, że i JRuby (wspominając jedynie bardziej popularne języki funkcyjne na JVM).

Jakby na dokładkę, na zakończenie lektury rozdziału 2. "Drinking From the Clojure Firehose" ze wspomnianej książki "The Joy of Clojure", w sekcji 2.7.5 trafiłem na makro .. (dwie kropki), które należy do rozwiązań wspierających programowanie w Clojure korzystając z dobrodziejstw inwentarza javowego przez możliwość wywoływania jej klas i metod. Pojawiła się myśl, aby zrealizować swoją pierwszą funkcję reverse, która jest bardziej ćwiczeniem na moją znajomość Clojure niż potrzebą praktyczną, szczególnie, że jej implementacja już istnieje w samym języku.

Zadanie było proste - wypisz ostatni element listy i tak rekurencyjnie w górę, co szybko okazało się nietrywialne, aby ostatecznie wrócić do lekko zmodyfikowanego, początkowego myślenia, że nie było, ale już jest i będzie proste. Nie od dziś wiadomo, że nauka, np. języka programowania, to ciągłe ćwiczenia i pasmo porażek, które z czasem przerzedzane jest coraz dłuższą serią sukcesów, a nieodzownym elementem jest analiza gotowych rozwiązań - w moim przypadku źródła Clojure, a w zasadzie wywołanie funkcji (source).

Niech moja sesja w REPL będzie historią mojej nauki implementacji reverse.
user=> (doc last)
-------------------------
clojure.core/last
([coll])
  Return the last item in coll, in linear time
nil

user=> (doc rest)
-------------------------
clojure.core/rest
([coll])
  Returns a possibly empty seq of the items after the first. Calls seq on its
  argument.
nil

user=> (doc if)
-------------------------
if
Special Form
  Please see http://clojure.org/special_forms#if
nil

; Tak podszedłem do mojej pierwsze wersji reverse

user=> (defn my-reverse [lista]
  (if (empty? (rest lista)) nil)
    (last lista))

; Tutaj, jakby to ująć, moja wiedza się skończyła...niestety
; a miało być coś z recur, stąd ta forma if

user=> (my-reverse '(1 2 3))
3

; upływa kilka minut, a ja nie mam pomysłu na kontynuowanie ćwiczenia, poza jednym...

user=> (source reverse)
(defn reverse
  "Returns a seq of the items in coll in reverse order. Not lazy."
  {:added "1.0"}
  [coll]
    (reduce conj () coll))
nil

; Co?! Tylko jedna linijka? reduce znam i conj również, ale w conj musi być tajemna moc odwracania!

user=> (doc conj)
-------------------------
clojure.core/conj
([coll x] [coll x & xs])
  conj[oin]. Returns a new collection with the xs
    'added'. (conj nil item) returns (item).  The 'addition' may
    happen at different 'places' depending on the concrete type.
nil

; Ach, to dodawanie może być specyficzne - zawsze na początku

user=> (class ())
clojure.lang.PersistentList$EmptyList

; Sprawdźmy działanie conj
 
user=> (conj () 1)
(1)
user=> (conj () 1 2)
(2 1)

; I wszystko (stało się) jasne. Znajomość API zawsze w cenie. Dla zainteresowanych: Zrealizuj conj.
Przy okazji: pisząc "source" pomyliłem się i napisałem "course". Jak widać, mamy jedynie jedną (!) permutację, aby jedno dawało drugie - dosłownie i w przenośni :)

Niech jednak Cię nie zwiedzie myśl, że zapomniałem się w tym poszukiwaniu wzniosłości programowania funkcyjnego. Moje wysiłki zostaną uwieńczone dopiero wtedy, kiedy uda mi się połączyć siłę serwerów aplikacyjnych Java EE z Clojure, a jedynym z możliwych i niezwykle ciekawych rozwiązań jest takie tworzenie aplikacji webowych, aby składanie strony odbywało się w wielu wątkach, równolegle. To jest już możliwe w Javie, ale z Clojure, programowanie wielowątkowe wydaje się być prostsze. Kłania się coś ala map/reduce dla aplikacji webowych. Jakby w gratisie (to słowo ma dla mnie specjalne znaczenie - patrz 4-dniówka na Podlasiu - pływające krowy, grzybobranie, kajaki i przepiękne krajobrazy), Koziołek ze swoim ostatnim wpisem na blogu podsunął mi myśl, aby pożenić Clojure po stronie serwera z GWT, albo (nieznanym mi bliżej, poza nazwą) Vaadin. Taki pomysł był forowany przy Grails, w którym tworzenie usług to trywiał (znaczne zmniejszenie czasu na ich stworzenie), a wizualizacją zajmowałoby się inne, dedykowane do tego zadania, rozwiązanie, np. GWT.

02 września 2010

Dojrzewam funkcyjnie z Clojure, a nawet udzielam pomocy!

5 komentarzy
To, że ja uczę się myślenia funkcyjnego poznając Clojure każdy czytelnik tego bloga wie, ale że ja pomagam *komuś*, to już niekoniecznie. Wczoraj wszedłem na wyższy stopień wtajemniczenia i nie tylko ja od kogoś, ale też inni ode mnie! Dzięki uprzejmości Marcina Rz. (nazwisko znane redakcji i nie podam, choćby mnie przypalano - obietnica to obietnica) mogłem wykazać się swoją znajomością Clojure (znaczy się - pytanie było wystarczająco trywialne, abym sobie poradził):

Po pobraniu wersji 1.2 Clojure i Clojure-contrib mam problem z używaniem biblioteki repl-utils. Zależało mi przede wszystkim na funkcji "show" z tej biblioteki.

marcin@xxx:/Shared/volumes/home/marcin/Devel/Clojure/clojure-1.2.0$
/usr/lib/jvm/java-6-sun/bin/java -cp clojure-1.2.0.jar:clojure-contrib-1.2.0.jar clojure.main
Clojure 1.2.0
user=> (use 'clojure.contrib.repl-utils)
java.lang.IllegalStateException: source already refers to: #'clojure.repl/source in namespace: user (NO_SOURCE_FILE:0)

Musiałem wyciąć z clojure-contrib-1.2.0/src/main/clojure/clojure/contrib/repl_utils.clj makro "source", funkcję "apropos" i "javadoc", dalej przebudować clojure-contrib, aby doprowadzić to do działania. Dopiero wtedy mogłem używać biblioteki 'clojure.contrib.repl-utils

Czy u Pana też występuje podobne zjawisko? Zastanawia mnie czy to było konieczne czy problem nie leży gdzieś indziej ? Chętnie zasięgnąłbym Pana opinii.


Ten "Pan" od razu wzmógł moje szare (stare?) komórki do większej aktywności i po chwili (!) wyszło ode mnie:

Zamień na (require) i będzie cacy. Dodatkowo możesz skorzystać z :exclude lub :only w (use).

Co w zasadzie kończy temat, ale nie mogłem oprzeć się pokusie, aby nie zapytać:

Przy okazji, napisz, co skłoniło Cię do użycia Clojure? Jest tyle ciekawszych rzeczy... :)

I po chwili miałem już odpowiedź:

Od kilku lat mam do czynienia z Perl i technologiami Webowymi (AJAX, jQuery, ExtJS, XSLT, SQL, memcached, itd.). Pomimo wielu dobrodziejstw Perl nie jest wystarczający przy obecnym postępie technologicznym. Szukałem czegoś bardziej nowoczesnego, potrafiącego w jak najlepszy sposób wykorzystać możliwości sprzętu i oferującego innowacyjne mechanizmy, dzięki którym można pisać solidny kod mając przy tym wiele satysfakcji. Chciałem także dobrze poznać paradygmat funkcyjny. Interesowałem się z początku Erlangiem, Clojure znałem "ze słyszenia". Dopiero Janek z Fablo.pl zainteresował mnie innowacyjnym modelem współbieżności w Clojure. Zacząłem bardziej poznawać Clojure, kupiłem "Programming Clojure" Stuarta Hallowaya i... wciągnęło mnie. STM jest świetne, składnia języka zgrabna i czytelna. Clojure wprowadza wiele nowego jeśli chodzi o programowanie. Przy okazji, przypominam sobie wiele z Javy, z którą nie miałem do czynienia od czasów studiów ;)
Mam nadzieję, że jeszcze będę miał okazję pracować przy jakimś projekcie w Clojure, a może nawet zatrudnić się jako programista Clojure. Byłoby super. Jest jeszcze wiele do poznania w Clojure, ale zapału nie powinno zabraknąć.

Jeśli to możliwe, chętnie chciałbym się z Tobą dzielić swoimi doświadczeniami/problemami w Clojure.


Możnaby powiedzieć, że nawiązała się pomiędzy nami wspólna nić porozumienia, żeby nie powiedzieć przyjaźni (na razie technicznej, ale kto wie, jak się sprawy potoczą). Może brzmieć lekko ironicznie, ale podchodzę do tego z jak największą powagą i uwagą. Przy okazji ciekawa historia, która przypomina mi wiele innych z kręgów lispowych, w tym i wielu "Clojurian". Mam nieodparte wrażenie, że wiele osób, które wcześniej parały się Lispem zapragnęło wejść w świat Javy, w której bibliotek cała masa, a do tego przenośność. Clojure łączy oba światy doskonale, więc z nim można wykorzystać doświadczenie funkcyjne przy tworzeniu nowoczesnych rozwiązań. Paradoks polega na tym, że mogłoby się wydawać, że z nastaniem Javy i całego ruchu wokół niej, wiele języków zeszło do podziemi, a przynajmniej spowolniło swój rozwój, bo uwagę przykuwało now(sz)e i Lisperzy popadli w rynkową niełaskę - mniej zleceń, wszystko obiektowe, itp. Teraz, z Clojure, sprawa ma się znacznie inaczej. Ze wspomnianym Software Transactional Memory (STM) i w połączeniu z zasadami programowania funkcyjnego, w którym kładzie się nacisk na czystość funkcji (wszystko na czym pracują to parametry wejściowe i zero skutków ubocznych aka utrzymywanie stanu zewnętrznego poza funkcją), programowanie współbieżne staje się banalne, a jeśli nie takie dokładnie, to przynajmniej prostsze. Kiedy do tego dorzucimy homoikoniczność Clojure, w którym reprezentacja programu jest jednocześnie podstawową strukturą danych wykorzystywaną w języku (w Clojure będzie to znana wszystkim programistom dowolnego powszechnego języka - lista) i możliwość zmiany struktury programu w trakcie jego działania, owi "straceńcy" stają się liderami rynku! Idealny moment, aby przywołać tytuł pewnej amerykańskiej komedii "Nieoczekiwana zmiana miejsc". Czego wszystkim życzę!

Na pytanie "Czy pomogę?" zawsze odpowiadam, że tak. W tym konkretnym przypadku jestem wręcz zaszczycony móc komuś pomóc, nawet jeśli...sam jeszcze potrzebuję pomocy.

Dla zainteresowanych większym zaangażowaniem szarych komórek, coś z podwórka Clojure. Analizując pewną aplikację napisaną w Clojure trafiłem na użycie funkcji comp, mapcat oraz concat. Wszystkie należą do przestrzeni (pakietu) clojure.core, więc znać je trzeba.
devmac:~ jacek$ clj
Clojure 1.2.0
user=> (doc comp)
-------------------------
clojure.core/comp
([f] [f g] [f g h] [f1 f2 f3 & fs])
  Takes a set of functions and returns a fn that is the composition
  of those fns.  The returned fn takes a variable number of args,
  applies the rightmost of fns to the args, the next
  fn (right-to-left) to the result, etc.
nil
user=> (doc mapcat)
-------------------------
clojure.core/mapcat
([f & colls])
  Returns the result of applying concat to the result of applying map
  to f and colls.  Thus function f should return a collection.
nil
user=> (doc concat)
-------------------------
clojure.core/concat
([] [x] [x y] [x y & zs])
  Returns a lazy seq representing the concatenation of the elements in the supplied colls.
nil
user=> (concat [1 2] (list 3) [4 5])
(1 2 3 4 5)
user=> (mapcat + (concat [1 2] (list 3) [4 5]))
java.lang.IllegalArgumentException: Don't know how to create ISeq from: java.lang.Integer
(user=> (mapcat #(* % 2) (concat [1 2] (list 3) [4 5]))
java.lang.IllegalArgumentException: Don't know how to create ISeq from: java.lang.Integer
(user=> (mapcat #(list (* % 2)) (concat [1 2] (list 3) [4 5]))
Zostawiłem moje pierwsze próby zrozumienia mapcat w połączeniu z próbą definiowania funkcji anonimowych, co może pomóc i Tobie w rozwikłaniu zagadki.

Pytanie: Co będzie wynikiem działania ostatniej, 31. linii rozpoczynającej się od mapcat?

Odpowiedzi, pytania, sugestie mile widziane, najlepiej jako komentarz do tego wpisu. Zapraszam do zabawy!