<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>tag:plblog.danieljanus.pl,2019:category:programowanie</id>
  <title>Daniel Janus – programowanie</title>
  <link href="http://plblog.danieljanus.pl/category/programowanie/"/>
  <updated>2022-12-24T00:00:00Z</updated>
  <author>
    <name>Daniel Janus</name>
    <uri>http://danieljanus.pl</uri>
    <email>dj@danieljanus.pl</email>
  </author>
  <entry>
    <id>tag:plblog.danieljanus.pl,2022-12-24:post:jak-zostalem-programista</id>
    <title>Jak zostałem programistą</title>
    <link href="http://plblog.danieljanus.pl/jak-zostalem-programista/"/>
    <updated>2022-12-24T00:00:00Z</updated>
    <content type="html">&lt;div&gt;&lt;p&gt;Nazywam się Daniel Janus, mam 38 lat i od 31 lat jestem programistą.&lt;/p&gt;&lt;p&gt;Czasem się tak przedstawiam i czasem budzi to zaciekawienie, powątpiewanie albo jedno i drugie naraz. Wracam więc myślą do samych początków.&lt;/p&gt;&lt;p&gt;Pamiętam, jechaliśmy z ojcem autobusem. Mogłem mieć pięć, może sześć lat. Tata ni stąd, ni zowąd zaczął opowiadać mi o komputerach. Nowe, nieznane mi wcześniej słowo. Nie mam pojęcia, jaka była treść tej opowieści, za to dokładnie pomiętam żarówki, które zapaliły mi się w oczach. I ten kawałek rozmowy:&lt;/p&gt;&lt;p&gt;– Jakby czarodziejski???&lt;br&gt; – Jakby czarodziejski.&lt;/p&gt;&lt;p&gt;Minęło kilkadziesiąt milionów sekund. Zdaje się, że łaziłem w międzyczasie i mówiłem, że chcę komputer. I w końcu, w 1991, tata przyjechał z saksów w Niemczech i mówi: kupiłem ci komputer! Nazywał się Commodore 64 i pachniał plastikiem, elektroniką i świeżością. Dzisiejsze komputery już tak nie pachną.&lt;/p&gt;&lt;figure&gt;&lt;img src="/img/blog/c64-box.jpg" alt="Pudełko od C64"&gt;&lt;figcaption&gt;To nie jest dokładnie TO pudełko – zdjęcie wygrzebałem z jakiejś aukcji na Allegro – ale tak wyglądało.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Zanim go uruchomiliśmy, upłynęły jeszcze prawie dwa tygodnie. W tym czasie w domu nastał nowy, kolorowy telewizor, a ja siedziałem jak na szpilkach, czekałem na ten telewizor, fantazjowałem o tym, jak to będzie, kiedy całość już podłączymy, obmacywałem zawartość pudełek (było jeszcze drugie, ze stacją dyskietek), zgadywałem, który klawisz do czego służy, i czytałem dołączone materiały. Oczywiście po niemiecku. I oczywiście nic z tego nie rozumiałem – niemieckiego miałem się zacząć uczyć w podstawówce dopiero od piątej klasy.&lt;/p&gt;&lt;p&gt;Były tam też wypisane różne rzeczy, które komputer może wyświetlić na ekranie, i takie, które trzeba do niego wklepać z klawiatury, żeby jakoś zareagował.&lt;/p&gt;&lt;figure&gt;&lt;img src="/img/blog/c64-book.jpg" alt="Commodore 64 Bedienungshandbuch: okładka"&gt;&lt;figcaption&gt;Zdjęcie tym razem z eBaya.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;W końcu nadszedł TEN dzień. Komputer podłączony, telewizor włączony, szukamy odpowiedniej częstotliwości wideo… i wreszcie z telewizyjnego białego szumu wyłonił się obraz, znany doskonale wielu ludziom z mojego pokolenia:&lt;/p&gt;&lt;figure&gt;&lt;img src="/img/blog/c64-start.webp" alt="Ekran startowy C64"&gt;&lt;figcaption&gt;Ekran startowy C64. Kto go pamięta?&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Nie pamiętam, co zrobiłem potem. Pewnie nacisnąłem wszystkie klawisze po kolei i nauczyłem się wyłączać i włączać całość. Jakoś intuicyjnie wykminiłem, jak działają klawisze kursora, co robi spacja, a co RETURN. Potem próbowałem przepisywać z tego niemieckiego podręcznika różne polecenia i patrzyć, co się stanie.&lt;/p&gt;&lt;p&gt;W którymś momencie odkryłem, że po wpisaniu &lt;code&gt;LOAD "$",8&lt;/code&gt; (dlaczego akurat 8? dlaczego akurat dolar? strasznie mnie to ciekawiło, ale nie wnikałem w szczegóły) i naciśnięciu RETURN komputer wypisuje na ekranie nowy, nieznany mi komunikat błędu, ale nie robi tego od razu – najpierw stacja dysków wydaje kilka dźwięków i zaczyna mrugać diodą na czerwono. Włożyłem dyskietkę do napędu (z komputerem przyjechało kilka dyskietek demonstracyjnych) i spróbowałem jeszcze raz. Tym razem zamigotało na zielono i komunikat był inny!&lt;/p&gt;&lt;p&gt;Wpisałem &lt;code&gt;LIST&lt;/code&gt;, tak jak kazał podręcznik (nadal nie bardzo rozumiejąc, co właściwie się dzieje). Commodore wyświetlił długą serię komunikatów. Były tam jakieś liczby, skróty i nazwy. Wtedy po raz kolejny wpatrzyłem się w ten kawałek podręcznika:&lt;/p&gt;&lt;figure&gt;&lt;img src="/img/blog/c64-instrukcja.jpg"&gt;W Internecie wbrew powiedzeniu czasem giną rzeczy, ale instrukcja do C64 na szczęście ocalała.&lt;/figure&gt;
&lt;p&gt;Zaraz… &lt;code&gt;PROGRAMMNAME&lt;/code&gt;? NAME? Imię? Nazwa?&lt;/p&gt;&lt;p&gt;Napisałem &lt;code&gt;LOAD "LASER FORCE",8&lt;/code&gt;. Instrukcji o dokładnie takim brzmieniu nie było w podręczniku! Ale &lt;code&gt;"LASER FORCE"&lt;/code&gt; to była jedna z nazw, bodaj ostatnia, w tym długim komunikacie – i pomyślałem, że można wstawić ją zamiast owego enigmatycznego &lt;code&gt;PROGRAMMNAME&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Strzał w dziesiątkę. Komputer odpowiedział komunikatem z podręcznika i zamyślił się na dłuższą chwilę, stacja dysków znowu zaśpiewała zgrzytliwą piosenkę. Czułem, że jestem na dobrym tropie.&lt;/p&gt;&lt;p&gt;Wreszcie wpisałem &lt;code&gt;RUN&lt;/code&gt;. Uruchomiłem grę! Zbiegła się cała rodzina.&lt;/p&gt;&lt;iframe width="100%" height="500" src="https://www.youtube.com/embed/M5tpEAW_zzA" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;Dokonałem wtedy swoich dwóch pierwszych odkryć programistycznych. Być może były to najdonioślejsze odkrycia w całej mojej karierze.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;span&gt;Mogę wpisać inną nazwę i załaduje się inny program. A mówiąc ogólniej: różnym parametrom polecenia odpowiadają różne skutki. Inne wejście – inne wyjście.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;Warto eksperymentować! Prawdopodobnie nic nie popsuję, najwyżej wyskoczy jakiś błąd i zawsze mogę spróbować jeszcze raz.&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Było też odkrycie trzecie, zupełnie niezwiązane z programowaniem:&lt;/p&gt;&lt;ol start="3"&gt;&lt;li&gt;Lubię grać w gry.&lt;/li&gt;&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;W następnych tygodniach wypróbowałem wszystkie programy i gry na tych kilku dyskietkach, które miałem. Ale skąd wziąć następne dyskietki? W roku 1991 w małym miasteczku (które jeszcze wtedy było wsią) nie było łatwo o dostęp do oprogramowania. Równolegle z graniem próbowałem więc przepisywać następne programy z podręcznika.&lt;/p&gt;&lt;p&gt;Niedługo potem kilku innych chłopaków z sąsiedztwa też dostało C64. Byłem jednak jedyną osobą ze stacją dysków i ten fakt zaważył na mojej przyszłości – wszyscy inni mieli magnetofony. O ile łatwo było po prostu wymieniać się kasetami, o tyle w moim przypadku pozyskanie nowych gier wymagało wyprawy do kolegi i zabrania ze sobą stacji. Rodzice mi nie pozwalali. Samemu? Ruchliwą ulicą? Z drogim sprzętem?&lt;/p&gt;&lt;p&gt;W międzyczasie tata wytrzasnął skądś stare numery „Bajtka”. Był tego pokaźny stosik, archiwa z kilku lat. „Bajtek” odegrał ogromną rolę w mojej wczesnej edukacji informatycznej: były tam małe kawałki wiedzy podane w przystępny sposób. Nie wszystko rozumiałem, ale przynajmniej było po polsku. I rubryka „Tylko dla przedszkolaków” tłumaczyła nie tylko to, co robią pomieszczone tam programy, ale również – dlaczego tak, a nie inaczej.&lt;/p&gt;&lt;p&gt;Dostałem też rozmaite książki – o BASIC-u i o samym komputerze, w tym polską biblię C64, „Commodore 64” Bohdana Frelka. Różne kawałki wiedzy zaczęły składać mi się w głowie w całość. Eksperymentowałem. Próbowałem zmieniać różne rzeczy w programach z Bajtka i w końcu pisać własne. Pamiętam, że jeden z moich programików z tamtych czasów nazywał się &lt;code&gt;ABC FIZYKI&lt;/code&gt; i wyświetlał definicje jednostek SI, które przepisałem z jednej z książek ojca.&lt;/p&gt;&lt;p&gt;Kiedy kilka lat później – bodaj w roku 1996 – przesiadłem się na PC, znałem już zupełnie nieźle BASIC i zaczynałem kumać, o co chodzi w asemblerze 6502. Stary komputer jednak poszedł w odstawkę. Rodzice sprzedali go niedługo potem, ale do dziś, po prawie ćwierćwieczu, potrafię bez zaglądania do internetu wyrecytować zgrubną mapę pamięci C64, powiedzieć, do których komórek pamięci trzeba wpisać jaki bajt, żeby tekst wyświetlał się na biało na czarnym tle z czarną ramką (mój ulubiony schemat kolorów z tamtych czasów), jaki jest kod znaku PETSCII odpowiedzialnego za czyszczenie ekranu oraz która procedura Kernala odpowiada za restart systemu, a która za wypisanie znaku na ekran.&lt;/p&gt;&lt;p&gt;Na pececie uczyłem się Pascala. Ale to już zupełnie inny rozdział tej historii.&lt;/p&gt;&lt;/div&gt;</content>
  </entry>
  <entry>
    <id>tag:plblog.danieljanus.pl,2014-05-06:post:kod-gietki</id>
    <title>Kod giętki</title>
    <link href="http://plblog.danieljanus.pl/kod-gietki/"/>
    <updated>2014-05-06T00:00:00Z</updated>
    <content type="html">&lt;div&gt;&lt;p&gt;Ludzie dziwnie na mnie patrzą, kiedy powiadam, że programowanie i poezja mają ze sobą wiele wspólnego. A jednak uważam, że tak właśnie jest, i to co najmniej na kilku poziomach.&lt;/p&gt;&lt;p&gt;Jaki jest cel poezji? Na ten temat napisano morze: parafrazując stary dowcip – gdzie dwóch poetów, tam co najmniej trzy odpowiedzi na to pytanie. Spróbuję jednak uogólnić: poezja ma za zadanie &lt;em&gt;wzbudzić&lt;/em&gt; w odbiorcy określone &lt;em&gt;emocje&lt;/em&gt; lub przemyślenia, używając do tego celu &lt;em&gt;słów&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;Warto zwrócić uwagę na tę drugą część, oczywistą przecież, poprzedniego zdania. Język, słowa, to najważniejszy i jedyny środek, jaki poezja ma do dyspozycji do osiągnięcia swojego celu. Dobór słów, kolejność słów, ułożenie słów – wszystko to ma w poezji kolosalne i fundamentalne znaczenie. Pojedyncze słowa są na wagę złota. Kilka celnie dobranych słów potrafi wyindukować mocny i poruszający obraz; i odwrotnie – jedno niepasujące słowo może ów obraz zaprzepaścić.&lt;/p&gt;&lt;p&gt;Kojarzycie ten wyimek z Norwida? Pewnie, że kojarzycie, w końcu zdobił pracownię języka polskiego w co drugiej szkole.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Ponad wszystkie wasze uroki,&lt;br&gt; Ty! Poezjo, i Ty, Wymowo,&lt;br&gt; Jeden – wiecznie będzie wysoki:&lt;br&gt; Odpowiednie dać rzeczy – słowo!&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Zastanówmy się nad tym chwilę. Norwid próbuje tu wyrazić właśnie to: owo właściwe poetom maksymalistyczne dążenie do przekazania myśli w sposób możliwie najbardziej oddziałujący na odbiorcę przy użyciu &lt;em&gt;odpowiednich słów&lt;/em&gt;. Tak i tylko tak.&lt;/p&gt;&lt;p&gt;Spójrzmy na kilka przykładów poszukiwania takich słów. Oto przykład zaczerpnięty z „Ocalonego w tłumaczeniu” Barańczaka, Gerard Manley Hopkins w liście do Roberta Bridgesa z 7 września 1888:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;By the bye you misquote the same modern author in writing ‘airy between towers’: what is not so? it should be ‘branchy between towers’.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Gwoli wyjaśnienia: „the same modern author” to sam Hopkins, a wiersz, o którym mowa, to „&lt;a href="http://www.bartleby.com/122/20.html"&gt;Duns Scotus's Oxford&lt;/a&gt;”, którego incipit adresat listu błędnie cytuje gdzie indziej. Poeta czepia się jednego jedynego słowa, uznając jego zamianę za niefortunną i istotnie osłabiającą przekazywany obraz, w  znamiennym proteście: „powietrzny pomiędzy wieżami – a cóż takie nie jest?” W domyśle: jeśli nad jakimś krajobrazem górują wieże, to jasne jest, że pomiędzy ich najwyższymi partiami jest dużo przestrzeni, nie ma sensu tego &lt;em&gt;explicite&lt;/em&gt; mówić w wierszu; ale Oksford z jego strzelistymi drzewami i rozłożystymi konarami jest w umyśle Hopkinsa bardzo charakterystyczny.&lt;/p&gt;&lt;p&gt;A teraz zacytuję fragment wiersza, o którym Theodore Roosevelt powiedział, że „nie jest pewien, czy go rozumie, ale z całą pewnością go lubi”: „&lt;a href="http://www.poetryfoundation.org/poem/175762"&gt;Luke Havergal&lt;/a&gt;” E. A. Robinsona. Wersy 4-5 w pierwszej redakcji tego wiersza, z roku 1896, brzmiały:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;The wind will moan, the leaves will whisper some,— &lt;br&gt; Whisper of her, and strike you as they fall; &lt;br&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Obraz jest sugestywny: niemal czuje się przenikliwy wiatr i  słyszy szelest liści układający się w szept. Ale czytając ostateczny, późniejszy o ćwierć wieku wariant wiersza, widzimy, że Robinson ten fragment nieznacznie przerobił:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;The leaves will whisper there of her, and some, &lt;br&gt; Like flying words, will strike you as they fall; &lt;br&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;W tej wersji wiatr obecny jest tylko w sposób dorozumiany: skoro mamy szept liści, to, jak uznał Robinson, wiadomo, że wieje i wspominanie o tym wprost jest redundantne; pominięte jest też powtórzenie „szeptu”, którego efekt wzmacniający przekaz był wątpliwy. Wyrzucając te dwie rzeczy, poeta robi miejsce na intrygującą i mocną metaforę „smagających słów w locie”, którą można interpretować na rozmaite sposoby i która interesująco współgra z dalszymi częściami wiersza („God slays Himself with every leaf that flies” w strofie drugiej).&lt;/p&gt;&lt;p&gt;Mieliśmy więc działający, mocny tekst; dzięki przeróbce i znalezieniu &lt;em&gt;odpowiedniego słowa&lt;/em&gt; działa on &lt;em&gt;lepiej&lt;/em&gt;, efektywniej. Punkt dla tych, którym przychodzi do głowy słowo „refactor”.&lt;/p&gt;&lt;p&gt;Zastanawialiście się, co by było, gdyby Mickiewicz używał gita? „Stepy akermańskie” były refaktorowane wielokrotnie w karkołomnych próbach uchwycenia balansu między wzburzonym oceanem a wysuszoną, jałową pustynią. Jesteśmy niemal w stanie zrekonstruować wynik działania &lt;code&gt;git log -p&lt;/code&gt; na tym hipotetycznym repozytorium.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;commit 90023f50e466ecf9339c1348f92bd855ccd58750
Author: Adam Mickiewicz &lt;adam@mickiewicz.lt&gt;
Date:   Wed Mar 12 19:42:12 1828 +0100

    pierwszy wers - wersja ostateczna

diff --git a/01-stepy-akermanskie.txt b/01-stepy-akermanskie.txt
index 019e416..0c741b8 100644
--- a/01-stepy-akermanskie.txt
+++ b/01-stepy-akermanskie.txt
@@ -1 +1 @@
-Okrążyły mię stepy na kształt oceanu
+Wpłynąłem na suchego przestwór oceanu

commit 1fee11618a8373ae8b8a435c6ff0c7009e3c4554
Author: Adam Mickiewicz &lt;adam@mickiewicz.lt&gt;
Date:   Fri Jun 2 17:15:26 1826 +0400

    poprawka do pierwszego wersu

diff --git a/01-stepy-akermanskie.txt b/01-stepy-akermanskie.txt
index 4b7d669..019e416 100644
--- a/01-stepy-akermanskie.txt
+++ b/01-stepy-akermanskie.txt
@@ -1 +1 @@
-Wjechałem na suchego stepy oceanu
+Okrążyły mię stepy na kształt oceanu

commit 416f8d15382b805def5e9e80928216bc86d56eec
Author: Adam Mickiewicz &lt;adam@mickiewicz.lt&gt;
Date:   Fri Jun 2 11:44:52 1826 +0400

    kolejna przeróbka pierwszego wersu

diff --git a/01-stepy-akermanskie.txt b/01-stepy-akermanskie.txt
index d3b53c2..4b7d669 100644
--- a/01-stepy-akermanskie.txt
+++ b/01-stepy-akermanskie.txt
@@ -1 +1 @@
-Żeglowałem i w suchych stepach oceanu
+Wjechałem na suchego stepy oceanu

commit c196b9483f501992ed9ce887b5ab4f1279c1b946
Author: Adam Mickiewicz &lt;adam@mickiewicz.lt&gt;
Date:   Thu Jun 1 18:03:31 1826 +0400

    przeróbka pierwszego wersu

diff --git a/01-stepy-akermanskie.txt b/01-stepy-akermanskie.txt
index d490bdf..d3b53c2 100644
--- a/01-stepy-akermanskie.txt
+++ b/01-stepy-akermanskie.txt
@@ -1 +1 @@
-Poznałem stepowego podróż oceanu
+Żeglowałem i w suchych stepach oceanu
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To, co próbuję pokazać na tych przykładach, to to, że dążenie do takiego użycia języka (polskiego, angielskiego, C, Javy...), żeby możliwie klarownie &lt;em&gt;wyrazić&lt;/em&gt; w nim to, o co chodzi, i żeby – cytując C.A.R. Hoare'a – rezultat miał „obviously no deficiencies”, jest wspólną cechą poetów i programistów.&lt;/p&gt;&lt;p&gt;Gdyby było inaczej, nie toczylibyśmy długich dyskusji na temat wyższości paradygmatu obiektowego nad funkcyjnym albo odwrotnie, nie wymyślalibyśmy takich rzeczy jak &lt;em&gt;dependency injection&lt;/em&gt; albo &lt;em&gt;fluent APIs&lt;/em&gt;, nie mielibyśmy tak bogatego katalogu wzorców projektowych, nie mielibyśmy indywidualnych upodobań stylistycznych, nie pisalibyśmy dokumentów o nazwie &lt;code&gt;CodingStyle&lt;/code&gt;, nie rozróżnialibyśmy pomiędzy kodem obleśnym i ładnym, wreszcie – widząc ten pierwszy – nie mielibyśmy odruchu, żeby go natychmiast przepisać.&lt;/p&gt;&lt;p&gt;„Chodzi mi o to, aby język giętki powiedział wszystko, co pomyśli głowa”, pisał Słowacki. Kiedy o tym myślę, przychodzi mi do głowy Lisp i często powtarzane zdanie, że to programowalny język programowania. Homoikoniczność i makra sprawiają, że pisząc program w Lispie, tworzymy nie tylko kod odpowiedzialny za rozwiązanie naszego problemu w z góry istniejącym języku, ale również rozwijamy i naginamy język, używając neologizmów: nowych, powstałych &lt;em&gt;ad hoc&lt;/em&gt; konstrukcji składniowych, o ile tylko pozwalają naturalniej, klarowniej wyrazić sens danego fragmentu kodu. Programowanie w Lispie jest jak bycie Leśmianem.&lt;/p&gt;&lt;p&gt;Czasem efekt estetyczny pojawia się w kodzie niejako mimochodem. Przypomina mi się wywiad, jakiego Guy Steele udzielił Peterowi Seibelowi w jego świetnej książce „Coders at Work” (polecam!) Steele wspomina tam o jednym z najbardziej satysfakcjonujących momentów w jego karierze programisty, kiedy po dwudziestu latach udało mu się skrócić napisaną przez Billa Gospera jedenastosłowową procedurę obliczającą sinusy i cosinusy liczb rzeczywistych o jedno słowo maszynowe. Osiągnąć, wydawałoby się, nieosiągalne, dzięki właściwej poezji zwięzłości i nadorganizacji języka: oto czysta radość programowania.&lt;/p&gt;&lt;p&gt;Warto się rozejrzeć po GitHubie. Poezja tam się czai.&lt;/p&gt;&lt;/div&gt;</content>
  </entry>
  <entry>
    <id>tag:plblog.danieljanus.pl,2013-02-01:post:tlumaczenia-technologiczne</id>
    <title>O książkach technologicznych i ich tłumaczeniach</title>
    <link href="http://plblog.danieljanus.pl/tlumaczenia-technologiczne/"/>
    <updated>2013-02-01T00:00:00Z</updated>
    <content type="html">&lt;div&gt;&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;Trudna rzecz jest jeden język drugim językiem dostatecznie wyrznąć.&lt;/em&gt;&lt;/p&gt;&lt;p style="text-align: right;"&gt;-- Jan Leopolita (1523--1572)&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="rodzaje-książek-informatycznych"&gt;Rodzaje książek informatycznych&lt;/h2&gt;&lt;p&gt;Mam zasadę dotyczącą książek informatycznych: nie kupuję niczego, co ma w tytule nazwę konkretnej technologii. (Purystów z góry przepraszam za użycie tego słowa, mając świadomość jego &lt;a href="https://groups.google.com/d/msg/pl.comp.programming/UH4hrQWBCPo/ILTtM_GEfO8J"&gt;pretensjonalności&lt;/a&gt;; ale nie znam innego polskiego określenia, które by zbiorczo obejmowało konkretne języki programowania, systemy operacyjne, platformy wykonawcze, serwery aplikacji, architektury itd.) Po pierwsze, technologie pojawiają się i znikają, wciąż ukazują się ich nowe wersje, i w efekcie czas życia książki opisującej, dajmy na to, konkretną implementację jakiegoś języka programowania na ogół bywa nie dłuższy niż kilka lat. Można wręcz mówić o „okresie półtrwania”, po którym połowa informacji zawartej w książce jest już nieaktualna.  Po drugie, i może ważniejsze, tego typu publikacje często wnoszą niewielką wartość dodaną w porównaniu do dokumentacji: od książki oczekuję, że nie tylko przekaże mi suche instrukcje, ale także idee, które w jakiś sposób wzbogacą mój warsztat i zostaną ze mną na dłużej. Dlatego do poduszki wolę poczytać o &lt;a href="http://www.amazon.co.uk/Kernel-Methods-Pattern-Analysis-Shawe-Taylor/dp/0521813972"&gt;uczeniu maszynowym za pomocą metod jądrowych&lt;/a&gt; niż o &lt;a href="http://www.amazon.co.uk/Pro-IBM-WebSphere-Application-Professionals/dp/1430219580"&gt;bebechach serwera aplikacyjnego&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Rzecz jasna, jak od każdej zasady, tak i od tej istnieją wyjątki. Bywa, że technologia na tyle wryła się w informatyczny krajobraz, że nie grozi jej szybkie przeterminowanie, a dana książka uznawana jest za kanoniczny opis technologii, nie zaś tylko dodatek &lt;em&gt;post hoc&lt;/em&gt; (jak &lt;a href="http://pl.wikipedia.org/wiki/J%C4%99zyk_ANSI_C"&gt;K&amp;R&lt;/a&gt; do C czy &lt;a href="http://en.wikipedia.org/wiki/Programming_Perl"&gt;Księga Wielbłądzia&lt;/a&gt; do Perla). Zdarza się też i tak, że książka opisuje język czy środowisko, którego akurat intensywnie używam lub przymierzam się do intensywnego używania, i wtedy mam szansę odnieść z niej dużą korzyść. Tak było z &lt;em&gt;&lt;a href="http://pragprog.com/book/shcloj/programming-clojure"&gt;Programming Clojure&lt;/a&gt;&lt;/em&gt; Stuarta Hallowaya, której pierwsze wydanie kupiłem niedługo po jego ukazaniu się z górą trzy lata temu, a drugie, zmienione i uaktualnione (a jakże!) wkrótce &lt;a href="http://helion.pl/ksiazki/programowanie-w-jezyku-clojure-stuart-halloway-aaron-bedra,proclo.htm"&gt;ukaże się po polsku&lt;/a&gt; nakładem &lt;a href="http://helion.pl/"&gt;Helionu&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Po przeczytaniu darmowej próbki nasunęły mi się wątpliwości odnośnie samego tłumaczenia, a w ślad za nimi myśli dotyczące polskich tłumaczeń literatury informatycznej w ogóle. Spróbuję je poniżej zebrać.&lt;/p&gt;&lt;h2 id="o-clojure-po-polsku"&gt;O Clojure po polsku&lt;/h2&gt;&lt;p&gt;Od razu powiem, że ogólny poziom przekładu Tomasza Walczaka uważam za wysoki: tekst jest spójny, nośny i pozbawiony błędów merytorycznych. Czepianie się szczegółów, które za chwilę nastąpi, ma być nie tyle krytyką, co raczej próbą uświadomienia sobie, dlaczego mimo wszystko „Programowanie w języku Clojure” czyta mi się mniej płynnie niż oryginał angielski; a także zwrócenia uwagi na problemy, z jakimi musi się zmierzyć tłumacz. Zasygnalizuję więc pewne zagadnienia, ale wcale nie mam pewności, czy potrafiłbym rozwiązać je lepiej, albo nawet – czy w ogóle się da.&lt;/p&gt;&lt;p&gt;Bo wydawałoby się, że tłumaczenie tekstu informatycznego jest czynnością bez porównania prostszą niż na przykład poezji, gdzie trzeba ważyć każde słowo, prawda? Tymczasem rodzi ono zupełnie nowe problemy. Tłumacz zmuszony jest często wymyślić od nowa terminologię z zakresu wąskiej dziedziny, o której po polsku się nie pisze lub pisze się rzadko lub nieformalnie; podjąć pewne nierzadko arbitralne decyzje i konsekwentnie się ich trzymać; albo – jak zobaczymy – zadecydować, co zrobić z subtelnymi różnicami odcieni znaczeniowych pewnych pojęć.&lt;/p&gt;&lt;h3 id="lispa-czy-lispu?"&gt;Lispa czy Lispu?&lt;/h3&gt;&lt;p&gt;Tłumacz często używa formy &lt;em&gt;Lispa&lt;/em&gt; jako dopełniacza od rzeczownika &lt;em&gt;Lisp&lt;/em&gt;. A jak naprawdę powinna brzmieć ta forma? Zdania na ten temat są podzielone. Wydaje się, że tradycyjnie w środowisku utrwalona była postać &lt;em&gt;Lispu&lt;/em&gt; (zapewne nawiązująca do odmiany nazw innych wczesnych języków programowania: &lt;em&gt;Fortranu&lt;/em&gt; i &lt;em&gt;COBOL-u&lt;/em&gt;), co potwierdza zarówno &lt;a href="http://poradnia.pwn.pl/lista.php?id=9081"&gt;poradnia językowa PWN&lt;/a&gt;, jak i słowniki: &lt;a href="http://sgjp.pl/"&gt;Słownik gramatyczny języka polskiego&lt;/a&gt; i słownik &lt;a href="http://www.sjp.pl/Lispu"&gt;sjp.pl&lt;/a&gt;. Forma &lt;em&gt;Lispa&lt;/em&gt; (być może urobiona przez analogię do odmiany nazw nowych języków: &lt;em&gt;Perla&lt;/em&gt;, &lt;em&gt;Pythona&lt;/em&gt;) też jest żywa w uzusie, choć, jak się zdaje, nieznacznie ustępuje odmianie &lt;em&gt;Lispu&lt;/em&gt; (w &lt;a href="http://nkjp.pl/"&gt;Narodowym Korpusie Języka Polskiego&lt;/a&gt; znajdujemy jej 19 wystąpień wobec 33 wystąpień &lt;em&gt;Lispu&lt;/em&gt;).&lt;/p&gt;&lt;p&gt;Dla kontrastu, słowo &lt;em&gt;Clojure&lt;/em&gt;, którego paradygmat odmiany (nieodmienne) nie budzi wątpliwości, pojawia się na ogół jako część sformułowania &lt;em&gt;język Clojure&lt;/em&gt;. Można by analogiczny zabieg przeprowadzić z &lt;em&gt;językiem Lisp&lt;/em&gt;. Albo pozostać przy formie &lt;em&gt;Lispu&lt;/em&gt;, która, choć zrazu brzmiała mi dziwnie, teraz wydaje się znacznie naturalniejsza niż &lt;em&gt;Lispa&lt;/em&gt;.&lt;/p&gt;&lt;h3 id="co-to-jest-instrukcja?"&gt;Co to jest instrukcja?&lt;/h3&gt;&lt;p&gt;Porównajmy taki fragment oryginału:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;code&gt;defrecord&lt;/code&gt; and related functions are covered in Section 6.3, &lt;em&gt;Protocols&lt;/em&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;z odpowiadającym mu tłumaczeniem:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Instrukcję &lt;code&gt;defrecord&lt;/code&gt; i powiązane funkcje omawiamy w podrozdziale 6.3, „Protokoły”.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Tłumacz – słusznie – uznaje, że po polsku to zdanie brzmiałoby koślawo bez wprowadzenia na początku rzeczownika określającego, cóż to jest owo &lt;code&gt;defrecord&lt;/code&gt;. Wybiera słowo „instrukcja”, które nie budziłoby żadnych sprzeciwów, gdyby to była książka o Pythonie albo C.  Tu jednak mam wątpliwości. Wiele języków programowania odróżnia wyrażenia od instrukcji, i w nich te drugie rozumiane są jako autonomiczne fragmenty wykonywalnego kodu potencjalnie zmieniającego stan programu. A przecież Clojure kładzie duży nacisk na niezmienność danych! Co więcej, w Clojure, jak we wszystkich Lispach i wielu innych językach funkcyjnych, tak rozumianych instrukcji nie ma (albo patrząc z innego punktu widzenia: termin ten jest tożsamy z terminem „wyrażenie”) -- kod programu po prostu składa się z wyrażeń.&lt;/p&gt;&lt;p&gt;Jeśli więc nie instrukcja, to co? W angielskim &lt;a href="http://clhs.lisp.se/Body/26_a.htm"&gt;żargonie lispowym&lt;/a&gt; używane jest słowo &lt;em&gt;form&lt;/em&gt; na określenie obiektu, który ma być poddany ewaluacji. Jeśli ten obiekt jest listą, której pierwszy element jest symbolem (i w takim razie składnia i semantyka listy są wyznaczone przez ten symbol, który może nazywać funkcję, makro albo wbudowaną konstrukcję języka), to nazwy tego symbolu używa się często jako przydawki. Mówi się więc o „&lt;code&gt;defmacro&lt;/code&gt; forms”, „&lt;code&gt;defvar&lt;/code&gt; forms”, itd. To właśnie to określenie mógł tu pominąć Halloway. A jak jest po polsku &lt;em&gt;form&lt;/em&gt;? Znowu: nie ma powszechnej zgody. Można by to mechanicznie przełożyć na &lt;em&gt;forma&lt;/em&gt;, ale moim zdaniem nazwą mniej przeładowaną znaczeniowo i bardziej oddającą sens jest &lt;em&gt;formuła&lt;/em&gt;. W użyciu są obie wersje i w Google można znaleźć ich przykładowe wystąpienia.&lt;/p&gt;&lt;p&gt;(Na marginesie można oddać tłumaczowi sprawiedliwość, że Halloway tą elizją zdaje się sugerować, że &lt;code&gt;defrecord&lt;/code&gt; jest funkcją, gdy w rzeczywistości jest to makro. Z drugiej strony, makra w Clojure są w istocie funkcjami, których &lt;em&gt;vars&lt;/em&gt; (patrz niżej) mają ustawioną odpowiednią flagę, co powoduje specjalne traktowanie ich przez kompilator.)&lt;/p&gt;&lt;h3 id="zmienna-zmiennej-nierówna"&gt;Zmienna zmiennej nierówna&lt;/h3&gt;&lt;p&gt;Znowu zacznę od cytatu, tym razem najpierw dwóch kawałków tłumaczenia:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Nie występują tu zmienne, modyfikowalny stan ani rozgałęzienia.&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;Przedrostek &lt;code&gt;#'&lt;/code&gt; oznacza, że funkcję zapisano w &lt;em&gt;zmiennej&lt;/em&gt; języka Clojure (...)&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;A tak wyglądają te fragmenty w oryginale:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;It has no variables, no mutable state, and no branches.&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;The prefix &lt;code&gt;#'&lt;/code&gt; indicates that the function was stored in a Clojure &lt;em&gt;var&lt;/em&gt; (...)&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;W polskim tłumaczeniu w obydwu tych fragmentach pojawia się słowo &lt;em&gt;zmienna&lt;/em&gt;.  Tymczasem spojrzenie na oryginał ujawnia, że słowo to ma dwa różne odcienie znaczeniowe, z których każdy oddawany jest po angielsku inaczej: raz jest to &lt;em&gt;variable&lt;/em&gt;, a raz &lt;em&gt;var&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;Co to jest &lt;em&gt;variable&lt;/em&gt;, wiadomo z innych języków: nazwane „pudełko” przechowujące wartość. (Zauważmy, że Halloway używa tego słowa mówiąc o zmiennych w Javie.)  Jest to termin ogólny i używany w kontekście wszystkich języków imperatywnych, jak C czy Perl. Natomiast &lt;em&gt;var&lt;/em&gt; jest terminem węższym i oznacza &lt;em&gt;jedną z clojurowych implementacji&lt;/em&gt; takiego pudełka. Dokładniej mówiąc, jest to obiekt klasy &lt;code&gt;clojure.lang.Var&lt;/code&gt;. Obiekty takie mają dwie charakterystyczne cechy, nietypowe dla zmiennych z innych języków, a odróżniające je od atomów, również podobnych do zmiennych:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;span&gt;wartości &lt;em&gt;var&lt;/em&gt; mogą być różne w różnych wątkach (przy czym istnieje koncepcja głównego wiązania, czyli wartości domyślnej, którą nowo utworzone wątki widzą dopóki jej sobie nie przesłonią własnym; takie wiązanie nie musi istnieć);&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;obiekty takie są pamiętane w przestrzeniach nazw: w istocie o przestrzeni nazw można myśleć jako o mapie wiążącej symbol z obiektem &lt;em&gt;var&lt;/em&gt;.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;(Taka dychotomia nazewnicza jest endemiczna dla Clojure i ma również miejsce przy funkcjach: w angielskim slangu clojurowym mówi się o &lt;em&gt;fns&lt;/em&gt;, czytane „ef-ens”, co oznacza obiekty implementujące interfejs &lt;code&gt;clojure.lang.IFn&lt;/code&gt;).&lt;/p&gt;&lt;p&gt;To dopiero dylemat dla tłumacza! Nie mam pojęcia, co mógłbym tu zaproponować. Można by pozostać przy długim, doprecyzowującym określeniu „zmienna &lt;em&gt;języka Clojure&lt;/em&gt;” dla odróżnienia od „zmiennej” po prostu, ale ceną za to jest rozwlekłość. Nie wiem, czy można jej jakoś uniknąć.&lt;/p&gt;&lt;h3 id="insert-into-użytkownik-(imię)-values-(&amp;apos;judyto&amp;apos;)"&gt;INSERT INTO użytkownik (imię) VALUES ('Judyto')&lt;/h3&gt;&lt;p&gt;Ach, kod wypluwający komunikaty w języku naturalnym! Błogosławieni użytkownicy angielszczyzny, albowiem oni odmianą przez przypadki martwić się nie muszą. Halloway, na nieszczęście tłumacza, wpada na pomysł uogólnienia kanonicznego „Hello, world” vel „Witaj, świecie” poprzez sparametryzowanie witanego obiektu.&lt;/p&gt;&lt;p&gt;Chciałoby się, żeby funkcja witająca zwracała się do użytkownika w wołaczu. Tłumacz osiąga to bez problemu:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;defn&lt;/span&gt; &lt;span class="hljs-title"&gt;hello&lt;/span&gt; [name]
  (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;str&lt;/span&gt;&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;Witaj, &amp;quot;&lt;/span&gt; name))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I świetnie. Tylko, że trzeba wołać tę funkcję tak: &lt;code&gt;(hello "Janku")&lt;/code&gt;, co jest trochę nienaturalne. A co, jeśli oprócz wyświetlenia powitania chcemy imię zapisać też w bazie danych? Z drugiej strony, &lt;em&gt;licentia poetica&lt;/em&gt; tłumacza nie może sięgać tak daleko, żeby walnąć tu&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;defn&lt;/span&gt; &lt;span class="hljs-title"&gt;witaj&lt;/span&gt; [imię]
  (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;str&lt;/span&gt;&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;Witaj, &amp;quot;&lt;/span&gt; (&lt;span class="hljs-name"&gt;odmie&lt;/span&gt;ń imię &lt;span class="hljs-symbol"&gt;:wo&lt;/span&gt;łacz)))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;!&lt;/p&gt;&lt;h2 id="warto,-nie-warto?"&gt;Warto, nie warto?&lt;/h2&gt;&lt;p&gt;Czy warto będzie kupić „Programowanie w języku Clojure” i przeczytać po polsku? Myślę, że tak, o ile ktoś jeszcze nie czytał po angielsku lub woli ojczysty język nawet kosztem drobnych nieścisłości. Jak już mówiłem, jestem pełen uznania dla pracy tłumacza i chylę czoła. Podziwiam!&lt;/p&gt;&lt;p&gt;Mam za to wątpliwości, czy warto &lt;em&gt;w ogóle tłumaczyć&lt;/em&gt; książki o technologiach. W pierwotnym zamyśle reszta niniejszej notki miała stanowić rozważania na ten temat, ale rozważania techniczne tak się rozrosły, że wpis zrobiłby się za długi. Więc o tym następnym razem.&lt;/p&gt;&lt;/div&gt;</content>
  </entry>
  <entry>
    <id>tag:plblog.danieljanus.pl,2012-12-19:post:o-jaskiniach-programowaniu-i-poezji</id>
    <title>O jaskiniach, programowaniu i poezji</title>
    <link href="http://plblog.danieljanus.pl/o-jaskiniach-programowaniu-i-poezji/"/>
    <updated>2012-12-19T00:00:00Z</updated>
    <content type="html">&lt;div&gt;&lt;p&gt;&lt;a href="http://bronikowski.com/2159/skutki-bycia-ekstremalnie-zajetym"&gt;Emil&lt;/a&gt; w swojej ostatniej notce wspomina, że jego marzeniem jest napisanie przygodówki tekstowej. Moim też! Z tej okazji przypomniał mi się eseik, który wysłałem pięć lat temu na jedną z grup usenetowych; publikuję tu bez zmian, dodając tylko odnośniki i poprawiając literówki. Odnotuję też, że większość materiału historycznego jest zaczerpnięta z „Inform Designer's Manual” Nelsona (1997), a programik, który cytuję na końcu, nie kompiluje się już ostatnimi wersjami I7, choć pewnie wymaga niewielkich przeróbek z zachowaniem formy.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;From: Daniel Janus &lt;przesunmalpe@nathell.korpus.pl&gt;
Newsgroups: pl.hum.poezja
Subject: # o jaskiniach, programowaniu i poezji
Date: Sat, 29 Sep 2007 09:32:45 +0000 (UTC)
Message-ID: &lt;slrnffs6tt.98o.przesunmalpe@students.mimuw.edu.pl&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Zaczęło się w 1838 roku.&lt;/p&gt;&lt;p&gt;Wtedy to właściciel Stephena Bishopa, niewolnika „szybkiego, śmiałego, entuzjastycznego”, wykupił ziemię w stanie Kentucky, na której terenie leżała Jaskinia Mamucia. Jej rzeczywisty rozmiar nie był wtedy jeszcze znany: nazwę nadali jej górnicy, wydobywający stąd ongi guano mamucie, z którego wyrabiano saletrę używaną do produkcji prochu. Stephenowi, samoukowi, człowiekowi bystremu, znającemu łacinę i grekę, powierzono opiekę nad jaskinią i w krótkim czasie stał się słynny jako władca podziemnego królestwa.&lt;/p&gt;&lt;p&gt;W kilka lat podwoił on znaną połać jaskini, nadał też nazwy -- półklasyczne, półamerykańskie – podziemnym korytarzom, rzekom, studniom: Rzeka Styks, Sala Lawinowa, Bezdnia, Wielka Kopuła. Wiele z tych nazw przetrwało do tej pory. Stephen oprowadzał po jaskini turystów, których zaczęło ściągać tam coraz więcej; mapa, którą naszkicował posiłkując się tylko własną pamięcią w roku 1842, była jeszcze w użyciu czterdzieści lat później. Stephen Bishop zmarł w roku 1857 jako wolny człowiek, uwolniony na mocy testamentu swojego właściciela.&lt;/p&gt;&lt;p&gt;Minął wiek. W czasie drugiej wojny światowej na terenach Jaskini ustanowiono park narodowy, co skutecznie odcięło napływ turystów. Coraz więcej osób jednak podejrzewało, że Jaskinia połączona jest systemem korytarzy z innymi okolicznymi jaskiniami, i poszukiwało przejścia; jednak bezskutecznie. Dopiero trzydzieści lat później, w roku 1972, Patricia Crowther, członkini ekspedycji badającej jaskinię Flint Ridge, przebrnęła przez ciasne, błotniste przejście i przedostała się do podziemnych głębi Jaskini Mamuciej; korytarz ten odkryto później ponownie na mapie Bishopa.&lt;/p&gt;&lt;p&gt;Trzy lata później mąż Patricii, William, programista w BBN (jednym z pionierów ARPAnetu, z którego później rozwinął się Internet), współuczestnik tamtej wyprawy, postanowił napisać program komputerowy, który jemu samemu przypominałby jaskiniowe lata, a zarazem byłby grą dla jego dzieci. Jak pisał później, miał to być program „nieodstraszający ludzi nieobeznanych z komputerem”, więc umożliwił wydawanie poleceń postaci kierowanej przez gracza w naturalnej angielszczyźnie, zamiast używania jakiegoś sztucznego zestawu komend. W grze Crowthera przemierzało się korytarze jaskini używając kierunków geograficznych; opisy pomieszczeń (z nazwami nadanymi jeszcze przez Bishopa) były zwięzłe, choć wielopoziomowe i bogate w szczegóły geologiczne. Crowther uczynił też niektóre przejścia niedostępnymi; aby się do nich dostać, należało pomyśleć nad rozwiązaniem ich zagadki i wykonać sekwencję określonych czynności.&lt;/p&gt;&lt;p&gt;Tak narodziła się interaktywna fikcja.&lt;/p&gt;&lt;p&gt;Mówię „interaktywna fikcja”, choć na początku używano terminu „gry przygodowe”, a później „przygodowe gry tekstowe” na określenie takich programów. I rzeczywiście były to gry przygodowe: pełne smoków, potworów, magii, ratowania księżniczek, dziejące się w najróżniejszych światach. (Zresztą program Crowthera został później podlany magiczno-baśniowym sosem i zorientowany raczej na zagadki niż na eksplorację, i to ta wersja jest najbardziej znana.) W latach osiemdziesiątych, wraz z upowszechnieniem się komputerów domowych, ten rodzaj rozrywki przeżył swoją świetność, głównie za sprawą firmy Infocom.&lt;/p&gt;&lt;p&gt;Okazało się jednak, że medium wymyślone przez Crowthera potrafi być czymś więcej, niż nośnikiem tylko dla gier przygodowych. Przykładem „&lt;a href="http://en.wikipedia.org/wiki/Photopia"&gt;Photopia&lt;/a&gt;” (Adam Cadre, 1998), opowiadająca historię o wolnej woli i determinizmie, w której gracz, choć znajduje się w centrum wydarzeń, nie ma na nie wielkiego wpływu (choć iluzja, że jest odwrotnie, jest silna); czy „&lt;a href="http://en.wikipedia.org/wiki/Galatea_(video_game)"&gt;Galatea&lt;/a&gt;” (Emily Short, 2000), dla której najwłaściwszym określeniem wydaje się być „studium psychologiczne”: jedno pomieszczenie, jedno światło padające z sufitu, protagonista i... ożywiona rzeźba na piedestale, z którą można rozmawiać na wiele sposobów. Interaktywna fikcja (już teraz określana właśnie tym terminem) odkryta została na nowo, jako rodzaj literatury. Jako -- Sztuka.&lt;/p&gt;&lt;p&gt;Po minionym boomie lat osiemdziesiątych zaczęły powstawać i rozpowszechniać się bezpłatnie systemy programistyczne ułatwiające tworzenie interaktywnej fikcji. Jednym z nich był Inform (Graham Nelson, 1993–): jego pionierska zasługa to skodyfikowanie ścisłych, choć poddających się elastycznym modyfikacjom w razie potrzeby, domyślnych reguł rządzących konstruowanym światem – modelu świata. System ten rozwijał się powoli, acz stabilnie, bez rewolucyjnych zmian, aż do wersji siódmej, kiedy Nelson postanowił radykalnie zmienić sposób tworzenia IF przy użyciu Informu. Co się zmieniło?&lt;/p&gt;&lt;p&gt;Aby używać wersji 1-6, trzeba było zostać programistą: nauczyć się specjalnej składni, obiektowo-imperatywnego paradygmatu programowania i sporej związanej z tym teorii. Inform 7 przyjmuje bardziej deklaratywne podejście i ukrywa sporą część pracy z tym związanej za fasadą języka, czyniąc następny krok w używaniu języka naturalnego: nie tylko w produkcie końcowym – ale też w kodzie źródłowym programu. Oto programy w Informie 7 pisze się w języku angielskim (albo do złudzenia go przypominającym), tyle tylko, że nieco bardziej skodyfikowanym.&lt;/p&gt;&lt;p&gt;Żeby nie być gołosłownym – oto pokój zawierający jabłko w Informie 6:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Constant Story "Hello World";
Constant Headline "^An Interactive Example^";

Include "Parser";
Include "VerbLib";

[ Initialise;
  location = Living_Room;
  "Hello World";
];

Object Room "Living Room"
with description "A comfortably furnished living room.",
has  light;

Object -&gt; Apple "juicy apple"
with name 'juicy' 'apple' 'fruit',
     description "Looks yummy.";
has  edible;

Include "Grammar";
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A oto ten sam pokój w Informie 7:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;"Hello World" by Daniel Janus

The story headline is "An Interactive Example".

The Living Room is a room. "A comfortably furnished living room."
The apple is an edible thing in the Living Room. Description of
the apple is "Looks yummy." Understand "juicy", "apple" and
"fruit" as the apple.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Stąd już niedaleko do poezji. Na pewnym blogu ogłoszone zostało wyzwanie: czy da się napisać rymujący się wiersz (choćby i z rymami w stylu Nasha), który jest &lt;em&gt;jednocześnie&lt;/em&gt; kompilującym się programem w Informie 7? Okazało się, że tak. I to nie byle jaki wiersz, bo sonet (a nawet metasonet – jego tematem są bowiem sonety!)&lt;/p&gt;&lt;p&gt;Oto on:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Will's Study is a room. The desk is here.
A hastily handwritten note is on it.
Description is "It's from your friend Shakespeare:
'I've gone to lunch. You'll have to write the sonnet.'"
Composing is an action applying to nothing.
The quill is a thing that is in the study.
Understand "write sonnet" as composing.
Description of the quill is "Old and cruddy".
Instead of composing when the player
has no quill, say "You have not got the quill."
Instead of composing, say "And... done. 'Heya',
says Will, returning. You say, 'Hello, Will!'
Says Shakespeare, 'Thank you for the time you've taken!
You really are a pal, Sir Francis Bacon.'"

-- Robin Johnson
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(Przypis: akcje w Informie są motorem zmiany stanu świata; przebieg akcji jest podzielony na kilka etapów – dokładnie specyfikuje to model świata, o którym pisałem wcześniej. Specyfikacja, że coś ma się wykonać „zamiast” jakiejś akcji, oznacza interferencję w model na pierwszym etapie, zanim zaczną działać normalne reguły.)&lt;/p&gt;&lt;p&gt;I moje rudymentarne tłumaczenie:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Dębowe biurko stoi w gabinecie Willa.
Na nim kartka z notatką skreśloną starannie.
Opis: "Od przyjaciela twojego, Szekspira:
„Poszedłem na kolację, napisz sonet za mnie.”"
Pisanie jest to akcja bezargumentowa.
Gęsie pióro to przedmiot, co jest w gabinecie.
Opis pióra: "Możesz nim ubrać myśli w słowa."
Powiedz: "Nie możesz pisać, pióra nie masz przecie!"
zamiast pisania, gdy gracz nie posiada pióra.
Zamiast pisania, powiedz: "I oto - skończone.
Will powraca z kolacji z uśmiechem ponurym,
lecz wnet się rozpromienia, widząc gotów sonet.
Uśmiechasz się, Will także, chyląc głowę w skłonie:
„Dobry z Waści przyjaciel, Franciszku Baconie.”"
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Oczywiście, to tylko połowa roboty, ta łatwiejsza – drugą będzie przeportowanie Informu 7 na język polski, tak aby powyższy wiersz dało się skompilować i sobie pograć :-)&lt;/p&gt;&lt;/div&gt;</content>
  </entry>
  <entry>
    <id>tag:plblog.danieljanus.pl,2012-09-28:post:perfekcyjna-niedoskonalosc-usosa</id>
    <title>Perfekcyjna niedoskonałość USOS-a</title>
    <link href="http://plblog.danieljanus.pl/perfekcyjna-niedoskonalosc-usosa/"/>
    <updated>2012-09-28T00:00:00Z</updated>
    <content type="html">&lt;div&gt;&lt;p&gt;&lt;a href="http://www.rp.pl/artykul/10,937291-Studenci-bez-cwiczen.html"&gt;Rzeczpospolita&lt;/a&gt; pisze o problemach z &lt;a href="http://usos.edu.pl/"&gt;USOS-em&lt;/a&gt;:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;– Poprawki oprogramowania nie są potrzebne, bo oprogramowania lepiej nie da się napisać – tłumaczy dr Janina Mincer-Daszkiewicz, kierownik Komisji ds. USOS z Uniwersytetu Warszawskiego. Jej zdaniem problem tkwi w liczbie logowań. – Jeśli w ciągu pierwszych pięciu minut trwania rejestracji na serwery uczelniane rzuca się 5 czy 10 tysięcy studentów, to i najlepszy komputer nie pomoże -- dodaje.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;a href="http://dict.pl/dict?word=mincer"&gt;Nazwisko&lt;/a&gt; pani Mincer-Daszkiewicz jest doskonale znane każdemu studentowi informatyki na Wydziale MIM UW, który w ciągu ostatnich kilkunastu lat zdawał egzamin z systemów operacyjnych: jej zasługi nie tylko dydaktyczne, ale również na polu informatyzacji obsługi Wydziału, a także całego UW, są nie do przecenienia. Tym bardziej dziwi powyższa wypowiedź.&lt;/p&gt;&lt;p&gt;I nawet nie chodzi mi o to, że stwierdzenie, że nie da się obsłużyć dziesięciu tysięcy użytkowników naraz, jest zwyczajnie nieprawdziwe (wystarczy sobie uświadomić wielkość liczby żądań, jakimi w każdej sekundzie bombardowane są serwery wyszukiwarki Google, albo ilu użytkowników naraz jest średnio zalogowanych do Facebooka; bez kłopotu osiągalne jest &lt;a href="http://www.kegel.com/c10k.html"&gt;10 tys. żądań na sekundę nawet na pojedynczej maszynie&lt;/a&gt;). Problemem jest raczej mentalność wyczuwalna w pierwszym zdaniu.&lt;/p&gt;&lt;p&gt;Kto pierwszy powiedział: „dzieła sztuki nie są nigdy ukończone – są porzucane”? Nie wiem; guglanie za „never finished, only abandoned” daje najrozmaitsze wyniki i pokazuje tę frazę odnoszoną do najrozmaitszych dziedzin, w tym do oprogramowania. Każdy, kto kiedykolwiek pracował nad dostatecznie dużym systemem, wie, jak bardzo jest to prawdziwe. Zawsze można coś poprawić: zwiększyć wydajność, odchudzić, dopracować; nie ma siódmego dnia, w którym można by odpocząć i popatrzeć z dumą na ukończone dzieło. Pani Mincer-Daszkiewicz zdaje się sądzić, że USOS osiągnął stan perfekcji; nie, nie osiągnął, podobnie jak nie osiągnął go żaden inny system.&lt;/p&gt;&lt;p&gt;A poprawiać jest co. Świadczy o tym pomruk niezadowolenia setek studentów, niemogących „wbić się” do USOSweb. Gdyby USOS realizowany był jako przedsięwzięcie komercyjne, to ze względu na to, że realia rynkowe wymuszają takie myślenie – ktoś by w końcu przyszedł i powiedział: „tak nie może być”. Nie może, bo bywa nieużywalne w fundamentalny sposób; nie może, bo udostępnienie produktu w takiej postaci klientom narazi przedsiębiorstwo na duże straty. Tłumaczenie się ograniczeniami architekturalnymi jest nie do przyjęcia: jeśli architektura w obecnej postaci nie pozwala na sensowne działanie systemu, to znaczy, że trzeba go przeprojektować.&lt;/p&gt;&lt;p&gt;Bardzo starałem się nie oddzielić grubą linią „podejścia biznesowego” od „akademickiego”, bo po obu stronach są i chlubne, i mniej chlubne przykłady. W ramach promocji tych pierwszych skorzystam z okazji i polecę stronę &lt;a href="http://tao.inf.ug.edu.pl/"&gt;dr Włodzimierza Bzyla&lt;/a&gt; z Uniwersytetu Gdańskiego; widać z niej pragmatyzm, duże zainteresowanie nowoczesnymi narzędziami i dążenie do przygotowywania studentów do późniejszej praktyki zawodowej -- słowem, autor &lt;em&gt;wie o co chodzi&lt;/em&gt;. Oby takich więcej!&lt;/p&gt;&lt;/div&gt;</content>
  </entry>
  <entry>
    <id>tag:plblog.danieljanus.pl,2012-01-10:post:praktyczne-uzycie-monady-state</id>
    <title>Praktyczne użycie monady state</title>
    <link href="http://plblog.danieljanus.pl/praktyczne-uzycie-monady-state/"/>
    <updated>2012-01-10T00:00:00Z</updated>
    <content type="html">&lt;div&gt;&lt;p&gt;Wreszcie rozumiem monady!&lt;/p&gt;&lt;p&gt;Pierwszy raz zetknąłem się z nimi dobrych osiem lat temu, przy okazji nauki Haskella; wtedy jednak nie starczyło mi cierpliwości, aby zaznajomić się z podstawami teoretycznymi. Sprawy nie ułatwiał fakt, że trudno o &lt;em&gt;naprawdę&lt;/em&gt; przystępne i zrozumiałe wprowadzenie do tego tematu: wygląda na to, że każdy adept Haskella, zrozumiawszy monady, pisze na ten temat własny &lt;em&gt;tutorial&lt;/em&gt;. Ja się powstrzymam od tego naturalnego odruchu i po prostu odeślę do niesamowicie szczegółowego, ośmioczęściowego &lt;a href="http://mvanier.livejournal.com/3917.html"&gt;cyklu artykułów&lt;/a&gt; autorstwa Mike'a Vaniera, który -- wreszcie! – sprawił, że coś mi „zaskoczyło” w umyśle. Zamiast tego w tym wpisie wynotuję najważniejsze spostrzeżenia, jakie zapamiętałem, a potem pokażę dwie wersje pewnego kodu operującego na sekwencjach bitów: niemonadyczną i napisaną z użyciem monady &lt;code&gt;state&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Mike zakłada znajomość Haskella, jak pisze we wstępie, na poziomie typów polimorficznych i klas typów; w praktyce jednak myślę, że do zrozumienia tekstu wystarczy pewne obycie z haskellową notacją (bardzo zresztą przypominającą zwykłą notację matematyczną), bo bardziej zaawansowane rzeczy są wyjaśniane w tekście na bieżąco. Bardzo polecam.&lt;/p&gt;&lt;p&gt;Oto więc moje notatki:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;span&gt;Kluczowe dla zrozumienia monad jest pojęcie &lt;em&gt;funkcji monadycznych&lt;/em&gt;; znacznie łatwiej jest je sobie intuicyjnie wyobrazić niż monadyczne wartości, zwracane przez &lt;code&gt;m-return&lt;/code&gt;. Mike podaje pewne intuicje także dla tych drugich, ale dla mnie ważne jest, żeby myśleć o monadach w kategoriach (wzbogaconych) funkcji, a nie wartości przez nie zwracanych.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;Łatwo wydefiniować przy użyciu prostego złożenia operacji &lt;code&gt;m-bind&lt;/code&gt; i &lt;code&gt;m-return&lt;/code&gt; funkcję składającą ze sobą dwie funkcje monadyczne. Przy użyciu tego operatora, który Mike nazywa &lt;code&gt;&lt;=&lt;&lt;/code&gt;, prawa rządzące monadami wyrażają się w elegancki i prosty do zapamiętania sposób: jest to operator łączny oraz &lt;code&gt;m-return&lt;/code&gt; jest jego lewą i prawą jedynką.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;I bodaj najistotniejsza rzecz – nie trzeba rozumieć monad, żeby ich używać. Niby oczywiste i niby tak właśnie pisałem do tej pory kod haskellowy, radośnie używając &lt;code&gt;do&lt;/code&gt;-notacji tak jakbym pisał kod imperatywny; ale okazuje się, że w ten sam sposób można z powodzeniem używać monady stanu i wyjaśnić, co ona robi, zupełnie w oderwaniu od całej reszty teorii monad. Wiedza na temat tego, co się dzieje „pod spodem”, jest konieczna tylko (i aż!) do zrozumienia, co poszczególne monady mają ze sobą wspólnego.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Reszta tego wpisu będzie poświęcona właśnie przykładowemu użyciu monady stanu w Clojure.  &lt;a href="http://plblog.danieljanus.pl/leniwa-wersja-makra"&gt;Pisałem wcześniej&lt;/a&gt;, że nie widziałem dotąd kodu, który dałby się wyrazić przejrzyściej zapisany z użyciem monad niż bez nich – i właśnie na taki kod się natknąłem.&lt;/p&gt;&lt;p&gt;Wyobraźmy więc sobie, że mamy strumień bitów. Dla prostoty zdefiniujmy sobie przykładowy strumień, na którym będziemy testować nasze funkcje, jako clojurowy wektor zer i jedynek.&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;def&lt;/span&gt; &lt;span class="hljs-title"&gt;v&lt;/span&gt; [&lt;span class="hljs-number"&gt;1&lt;/span&gt; &lt;span class="hljs-number"&gt;1&lt;/span&gt; &lt;span class="hljs-number"&gt;1&lt;/span&gt; &lt;span class="hljs-number"&gt;0&lt;/span&gt; &lt;span class="hljs-number"&gt;1&lt;/span&gt; &lt;span class="hljs-number"&gt;0&lt;/span&gt; &lt;span class="hljs-number"&gt;1&lt;/span&gt; &lt;span class="hljs-number"&gt;0&lt;/span&gt; &lt;span class="hljs-number"&gt;1&lt;/span&gt; &lt;span class="hljs-number"&gt;0&lt;/span&gt;])
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Chcemy naddać naszemu strumieniowi pewne uporządkowanie: zdekodować zera i jedynki, które z niego przychodzą, według jakiegoś kodu o zmiennej długości. Najbardziej oczywistym, znanym każdemu kodem jest kod binarny: czytamy ze strumienia ileś bitów, traktujemy je jako binarną (bez znaku) reprezentację pewnej liczby i zwracamy tę liczbę. Łatwo napisać w Clojure funkcję, która to robi.&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;defn&lt;/span&gt; &lt;span class="hljs-title"&gt;read-binary&lt;/span&gt; [n bits]
  (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;loop&lt;/span&gt;&lt;/span&gt; [res &lt;span class="hljs-number"&gt;0&lt;/span&gt; n n [fst &amp;amp; rst &lt;span class="hljs-symbol"&gt;:as&lt;/span&gt; bs] bits]
    (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;if&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;zero?&lt;/span&gt;&lt;/span&gt; n)
      [res bs]
      (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;recur&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;+&lt;/span&gt;&lt;/span&gt; res res fst) (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;dec&lt;/span&gt;&lt;/span&gt; n) rst))))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Spróbujmy wczytać z naszego strumienia czterobitową liczbę:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-name"&gt;read-binary&lt;/span&gt; &lt;span class="hljs-number"&gt;4&lt;/span&gt; v)
&lt;span class="hljs-comment"&gt;;=&amp;gt; [14 (1 0 1 0 1 0)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Czternaście. Ale zauważmy, że zdekodowana liczba nie jest jedyną zwracaną wartością! Żeby dało się z naszego strumienia odczytywać dalsze liczby, musimy zwrócić parę (wektor dwuelementowy): wartość odczytana plus &lt;em&gt;reszta strumienia&lt;/em&gt;, z której będziemy czytać dalej. Piszemy wszak funkcyjnie: nie zmieniamy naszych wartości w miejscu, tylko z jednych wartości produkujemy następne.&lt;/p&gt;&lt;p&gt;Innym rodzajem kodu jest &lt;em&gt;kod unarny&lt;/em&gt;: czytamy po prostu ze strumienia jedynki, aż napotkamy pierwsze zero – wtedy przestajemy czytać i naszą wartością jest liczba przeczytanych bitów. W ten sposób dowolną liczbę dodatnią &lt;em&gt;n&lt;/em&gt; da się zareprezentować na &lt;em&gt;n&lt;/em&gt; bitach. Implementacja jest równie prosta:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;defn&lt;/span&gt; &lt;span class="hljs-title"&gt;read-unary&lt;/span&gt; [bits]
  (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;loop&lt;/span&gt;&lt;/span&gt; [n &lt;span class="hljs-number"&gt;1&lt;/span&gt; [fst &amp;amp; rst] bits]
    (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;if&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;zero?&lt;/span&gt;&lt;/span&gt; fst)
      [n rst]
      (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;recur&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;inc&lt;/span&gt;&lt;/span&gt; n) rst))))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Sprawdzamy:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-name"&gt;read-unary&lt;/span&gt; v)
&lt;span class="hljs-comment"&gt;;=&amp;gt; [4 (1 0 1 0 1 0)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Trzy jedynki i zero, razem cztery skonsumowane bity, więc odczytaną liczbą jest 4. Działa.&lt;/p&gt;&lt;p&gt;Spróbujmy teraz skleić nasze funkcje, to znaczy odczytać ze strumienia dwie liczby: najpierw zakodowaną unarnie, a potem binarnie na sześciu bitach.&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;let&lt;/span&gt;&lt;/span&gt; [[x v1] (&lt;span class="hljs-name"&gt;read-unary&lt;/span&gt; v)
      [y v2] (&lt;span class="hljs-name"&gt;read-binary&lt;/span&gt; &lt;span class="hljs-number"&gt;6&lt;/span&gt; v1)]
  [x y])
&lt;span class="hljs-comment"&gt;;=&amp;gt; [4 42]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Wynik jest poprawny, ale kod nieelegancki: tak naprawdę nie interesują nas te wszystkie &lt;code&gt;v&lt;/code&gt;, &lt;code&gt;v1&lt;/code&gt;, &lt;code&gt;v2&lt;/code&gt;, które przepychamy przez nasze funkcje; potrzebujemy tylko przeczytanych liczb, a poszczególne części strumienia są nam tylko po to, żeby mieć z czego czytać. Im więcej składanych ze sobą operacji, tym łatwiej się pomylić.&lt;/p&gt;&lt;p&gt;Tu wkracza monada stanu, która umożliwia ukrycie tych pośrednich wartości i bardziej eleganckie złożenie &lt;code&gt;read-unary&lt;/code&gt; i &lt;code&gt;read-binary&lt;/code&gt;. Monadycznie zapisalibyśmy to jakoś tak:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-name"&gt;domonad&lt;/span&gt; state-m
         [x read-unary
          y (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;partial&lt;/span&gt;&lt;/span&gt; read-binary &lt;span class="hljs-number"&gt;6&lt;/span&gt;)]
         [x y])
&lt;span class="hljs-comment"&gt;;=&amp;gt; #&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Wygląda to bardzo podobnie. &lt;code&gt;domonad state-m&lt;/code&gt; jest jak &lt;code&gt;let&lt;/code&gt;. Tak jak chcieliśmy, nie przekazujemy naszego stanu (strumienia) &lt;em&gt;explicite&lt;/em&gt;; zamiast tego przy &lt;code&gt;x&lt;/code&gt; i &lt;code&gt;y&lt;/code&gt; podajemy funkcje, które mają być zawołane na aktualnym stanie, żeby uzyskać żądaną wartość i nowy stan.&lt;/p&gt;&lt;p&gt;No dobrze, ale gdzie tu miejsce na nasz stan początkowy? Jak go przekazać? Proste: powyższa formuła, jak widać z mało czytelnego wyniku, zwróciła jakąś funkcję. Wystarczy ją teraz zawołać na naszym początkowym stanie, aby uzyskać wynik wraz ze stanem końcowym:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-name"&gt;*1&lt;/span&gt; v)
&lt;span class="hljs-comment"&gt;;=&amp;gt; [[4 42] nil]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Jedyne, co wydaje się nadmiarowe w naszym złożeniu, to owo &lt;code&gt;partial&lt;/code&gt; pojawiające się wyżej. Jest to efekt uboczny faktu, że Clojure w odróżnieniu od Haskella rozróżnia funkcje jedno- i więcejargumentowe (tak dokładniej to w Haskellu występują wyłącznie funkcje jednoargumentowe). Skoro w &lt;code&gt;domonad state-m&lt;/code&gt; powinny na przemian pojawiać się symbole i funkcje jednoargumentowe biorące stan, to gdy nasza funkcja akceptuje coś jeszcze (tu: liczbę bitów), powinniśmy zrobić z niej funkcję jednoargumentową za pomocą &lt;code&gt;partial&lt;/code&gt;. Alternatywnie, jeżeli chcemy trzymać się stylu monadycznego, możemy przepisać &lt;code&gt;read-binary&lt;/code&gt; jako:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;defn&lt;/span&gt; &lt;span class="hljs-title"&gt;read-binary&lt;/span&gt; [n]
  (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;fn&lt;/span&gt;&lt;/span&gt; [bits]
    (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;loop&lt;/span&gt;&lt;/span&gt; [res &lt;span class="hljs-number"&gt;0&lt;/span&gt; n n [fst &amp;amp; rst &lt;span class="hljs-symbol"&gt;:as&lt;/span&gt; bs] bits]
      (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;if&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;zero?&lt;/span&gt;&lt;/span&gt; n)
        [res bs]
        (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;recur&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;+&lt;/span&gt;&lt;/span&gt; res res fst) (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;dec&lt;/span&gt;&lt;/span&gt; n) rst)))))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Teraz zamiast &lt;code&gt;(partial read-binary 6)&lt;/code&gt; możemy napisać po prostu &lt;code&gt;(read-binary 6)&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Teraz, kiedy umiemy już składać operacje stanowe, zilustrujemy to przy pomocy jeszcze jednego kodu, a właściwie rodziny kodów nazywanych &lt;em&gt;kodami Golomba&lt;/em&gt;. Po szczegółowy opis odsyłam do &lt;a href="http://pl.wikipedia.org/wiki/Kod_Golomba"&gt;Wikipedii&lt;/a&gt;, natomiast dla potrzeb tego artykułu wystarczy nam wiedza, że te kody są parametryzowane jedną liczbą &lt;em&gt;m&lt;/em&gt;. Liczba w kodzie Golomba jest podzielona na dwie części, z których pierwsza jest zakodowana unarnie, a druga -- nie większa niż &lt;em&gt;m&lt;/em&gt; – binarnie. W implementacji możemy więc skorzystać z naszych gotowych już funkcji &lt;code&gt;read-unary&lt;/code&gt; i &lt;code&gt;read-binary&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Bez dłuższych już komentarzy przedstawię dwie implementacje: eksplicytną i używającą monady stanu. Obie będą używać pomocniczej funkcji &lt;code&gt;read-bit&lt;/code&gt;, czytającej po prostu jeden bit ze strumienia i używanej tak samo jak &lt;code&gt;read-unary&lt;/code&gt; czy &lt;code&gt;read-binary&lt;/code&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;defn&lt;/span&gt; &lt;span class="hljs-title"&gt;read-bit&lt;/span&gt; [[bit &amp;amp; bits]]
  [bit bits])
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Najpierw wersja niemonadyczna:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;defn&lt;/span&gt; &lt;span class="hljs-title"&gt;read-golomb&lt;/span&gt; [m]
  (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;fn&lt;/span&gt;&lt;/span&gt; [bits]
    (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;let&lt;/span&gt;&lt;/span&gt; [k (&lt;span class="hljs-name"&gt;clog2&lt;/span&gt; m)
          r (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;-&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;bit-shift-left&lt;/span&gt; &lt;span class="hljs-number"&gt;1&lt;/span&gt; k) m)
          [a bits1] (&lt;span class="hljs-name"&gt;read-unary&lt;/span&gt; bits)
          [b bits2] ((&lt;span class="hljs-name"&gt;read-binary&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;dec&lt;/span&gt;&lt;/span&gt; k)) bits1)
          [ir bits] (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;if&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;&amp;lt;&lt;/span&gt;&lt;/span&gt; b r)
                      [b bits]
                      (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;let&lt;/span&gt;&lt;/span&gt; [[nb bits] (&lt;span class="hljs-name"&gt;read-bit&lt;/span&gt; bits)]
                        [(&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;+&lt;/span&gt;&lt;/span&gt; b b nb (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;-&lt;/span&gt;&lt;/span&gt; r)) bits]))]
      [(&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;+&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;*&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;dec&lt;/span&gt;&lt;/span&gt; a) m) ir) bits])))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A teraz używająca monad. Żeby nie przeplatać &lt;code&gt;let&lt;/code&gt; i &lt;code&gt;domonad&lt;/code&gt;, można po prostu zdefiniować funkcję zwracającą pewną stałą wartość i niezmieniony stan – nazwę ją &lt;code&gt;m-const&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;defn&lt;/span&gt; &lt;span class="hljs-title"&gt;m-const&lt;/span&gt; [x]
  (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;fn&lt;/span&gt;&lt;/span&gt; [state]
    [x state]))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Używając jej, możemy zaimplementować &lt;code&gt;read-golomb&lt;/code&gt; tak:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;defn&lt;/span&gt; &lt;span class="hljs-title"&gt;read-golomb-monadic&lt;/span&gt; [m]
  (&lt;span class="hljs-name"&gt;domonad&lt;/span&gt; state-m
           [k (&lt;span class="hljs-name"&gt;m-const&lt;/span&gt; (&lt;span class="hljs-name"&gt;clog2&lt;/span&gt; m))
            r (&lt;span class="hljs-name"&gt;m-const&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;-&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;bit-shift-left&lt;/span&gt; &lt;span class="hljs-number"&gt;1&lt;/span&gt; k) m))
            a read-unary
            b (&lt;span class="hljs-name"&gt;read-binary&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;dec&lt;/span&gt;&lt;/span&gt; k))
            nb (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;if&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;&amp;lt;&lt;/span&gt;&lt;/span&gt; b r) (&lt;span class="hljs-name"&gt;m-const&lt;/span&gt; &lt;span class="hljs-literal"&gt;nil&lt;/span&gt;) read-bit)]
           (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;+&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;*&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;dec&lt;/span&gt;&lt;/span&gt; a) m)
              (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;if&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;&amp;lt;&lt;/span&gt;&lt;/span&gt; b r) b (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;+&lt;/span&gt;&lt;/span&gt; b b nb (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;-&lt;/span&gt;&lt;/span&gt; r))))))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Czytelniej?&lt;/p&gt;&lt;/div&gt;</content>
  </entry>
  <entry>
    <id>tag:plblog.danieljanus.pl,2011-07-21:post:clojurescript</id>
    <title>ClojureScript</title>
    <link href="http://plblog.danieljanus.pl/clojurescript/"/>
    <updated>2011-07-21T00:00:00Z</updated>
    <content type="html">&lt;div&gt;&lt;p&gt;Rich Hickey, autor Clojure, wyrasta na Steve'a Jobsa nowoczesnych języków programowania – przynajmniej jeśli idzie o sposób prezentacji swoich dzieł. Oto kilka dni temu na grupie dyskusyjnej Clojure pojawiło się &lt;a href="https://groups.google.com/forum/#!msg/clojure/v2fP1qQQBhQ/Xz836_GafIgJ"&gt;ogłoszenie&lt;/a&gt;, że Rich wystąpi na najbliższym spotkaniu nowojorskiej grupy użytkowników Clojure, że strumień wideo z wystąpienia będzie nadawany na żywo i że zaprezentowane zostanie „coś nowego”.&lt;/p&gt;&lt;p&gt;Tym czymś okazał się &lt;a href="https://github.com/clojure/clojurescript"&gt;ClojureScript&lt;/a&gt;: kompilator Clojure do JavaScriptu, łączący moc pierwszego języka z powszechnością drugiego.&lt;/p&gt;&lt;p&gt;JavaScript bywa nazywany „czarnym koniem” języków programowania, najbardziej niedocenianym językiem minionej dekady i najważniejszym obecnej. Istotnie, silniki JavaScriptu w przeglądarkach – a także wolnostojące, jak Node.js – poczyniły w ostatnich latach kolosalne postępy, jeśli chodzi o wydajność, narzędzie to jest coraz chętniej używane do programowania coraz poważniejszych aplikacji i nie mijając się z prawdą można powiedzieć, że JavaScript jest wszędzie.&lt;/p&gt;&lt;p&gt;A teraz te same słowa można odnieść również do Clojure. W tegorocznym &lt;a href="http://cemerick.com/2011/07/11/results-of-the-2011-state-of-clojure-survey/"&gt;sondażu&lt;/a&gt;, podobnie jak rok temu, społeczność Clojure najczęściej wskazywała JavaScript jako hipotetyczną alternatywną wobec JVM platformę, ale zapewne mało kto się spodziewał, że urzeczywistni się to tak szybko. Łatwo sobie uzmysłowić możliwości, jakie daje nowa implementacja. Ten sam kod na serwerze i w przeglądarce? Programowanie aplikacji iOS w Clojure? Jak najbardziej!&lt;/p&gt;&lt;p&gt;W ramach dodawania łyżki dziegciu do tej beczki miodu trzeba zauważyć jednak, że dialekt ClojureScript jest okrojony i ma nieco inną semantykę niż standardowy Clojure: nie ma STM, niezmienność struktur danych nie jest wymuszana, wektory są zwykłymi JavaScriptowymi tablicami. Czy to się zmieni, pokaże przyszłość.&lt;/p&gt;&lt;p&gt;Jaka przyszłość czeka Clojure i ClojureScript? Spróbuję pokusić się o przepowiednię:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;span&gt;Pojawienie się alternatywnej wobec JVM platformy wymusi ukształtowanie się bogatszej niż do tej pory biblioteki standardowej Clojure, wspólnej dla obu wersji. Ten proces trwa już teraz: tam, gdzie w czasach Clojure 1.0 pisało się &lt;code&gt;(.toUpperCase "foo")&lt;/code&gt; (wychodząc z założenia &lt;em&gt;where Java isn't broken, Clojure doesn't fix it&lt;/em&gt;), teraz kanoniczną formą jest &lt;code&gt;(clojure.string/upper-case "foo")&lt;/code&gt;. W ten sposób Clojure stopniowo stanie się językiem niezabawkowym według &lt;a href="http://www.3ofcoins.net/2009/01/30/common-lisp-clojure-and-seriousness/"&gt;definicji Maćka Pasternackiego&lt;/a&gt;.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;ClojureScript otworzy dla Clojure zupełnie nową niszę: stanie się popularny wśród programistów-frontendowców, piszących do tej pory głównie w JavaScripcie lub CoffeeScripcie.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;Równocześnie pojawienie się ClojureScript nie oznacza, że zatrzymają się prace nad „tradycyjnym” Clojure. JVM pozostanie główną platformą rozwoju języka i to tam będą pojawiać się kolejne innowacyjne cechy. Wersja 1.3 jest już w fazie beta i nadanie jej statusu &lt;em&gt;final&lt;/em&gt; jest kwestią kilku tygodni.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Patrząc wstecz, aż trudno uwierzyć, jak dużą popularność zdobyło sobie Clojure. Gdyby ktoś mi cztery lata temu powiedział, że za cztery lata pojawi się nowy język z rodziny Lisp, który będzie znacznie bardziej elegancki niż Common Lisp, znacznie praktyczniejszy niż Scheme i używać go będą wielkie instytucje finansowe, wyśmiałbym go. A jednak: zaczynam wierzyć, że prawo Kopernika jednak nie stosuje się w informatyce.&lt;/p&gt;&lt;/div&gt;</content>
  </entry>
  <entry>
    <id>tag:plblog.danieljanus.pl,2011-02-12:post:smyrna</id>
    <title>Smyrna</title>
    <link href="http://plblog.danieljanus.pl/smyrna/"/>
    <updated>2011-02-12T00:00:00Z</updated>
    <content type="html">&lt;div&gt;&lt;p&gt;Dziś chciałbym zaprezentować kolejny stworzony przeze mnie przykład wykorzystania Clojure w praktyce – program &lt;a href="http://smyrna.danieljanus.pl"&gt;Smyrna&lt;/a&gt;. To proste narzędzie do przeszukiwania korpusów: umożliwia łatwe zindeksowanie zbioru dokumentów w formacie HTML, wyszukanie wystąpień interesującego nas leksemu i stworzenie listy frekwencyjnej słów.&lt;/p&gt;&lt;img src="/img/blog/smyrna.png"&gt;
&lt;p&gt;Program zrodził się z potrzeby chwili (potrzebowałem porównać wystąpienia pewnej grupy słów w różnych zestawach danych), ale uznałem, że jest na tyle użyteczny, że może przydać się nie tylko mnie. Zwłaszcza że nie było dotąd programu na wolnej licencji, który by umożliwiał &lt;em&gt;łatwe&lt;/em&gt; przeszukiwanie własnych zbiorów polskich tekstów. Można wykorzystać &lt;a href="http://poliqarp.sf.net"&gt;Poliqarpa&lt;/a&gt;, w którym też maczałem palce (a raczej nurzałem ręce), jednak używanie go z własnymi zbiorami danych wymaga ekwilibrystyki w stylu &lt;a href="http://github.com/nathell/dxces"&gt;dxces&lt;/a&gt; i jest trudne do przeskoczenia dla nietechnicznych użytkowników. Jest więc Smyrna swoistym uzupełnieniem dla Poliqarpa – &lt;a href="http://pl.wikipedia.org/wiki/Polikarp_ze_Smyrny"&gt;stąd zresztą nazwa&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Z technicznego punktu widzenia interesujący może być sposób, w jaki skonstruowałem interfejs użytkownika. Mimo że Smyrna jest w zasadzie aplikacją desktopową, obsługuje się ją przez przeglądarkę WWW: uruchamiany jest lokalny serwerek HTTP na porcie 8080, a następnie przeglądarka z tym adresem; cała dalsza komunikacja między JavaScriptowym kodem klienckim a Clojurowym silnikiem przeszukującym odbywa się przez JSON-RPC.&lt;/p&gt;&lt;p&gt;W ramach ćwiczenia, kliencki kod napisałem w nowej, alternatywnej składni dla JavaScriptu, czyli zyskującym coraz większą popularność &lt;a href="http://jashkenas.github.com/coffee-script/"&gt;CoffeeScripcie&lt;/a&gt;. Eksperyment uważam za udany: kod wychodzi czytelniejszy i zwięźlejszy niż w "zwykłym" JS.&lt;/p&gt;&lt;/div&gt;</content>
  </entry>
  <entry>
    <id>tag:plblog.danieljanus.pl,2011-01-31:post:czyszczenie-dowiazan-lokalnych</id>
    <title>Clojure: czyszczenie dowiązań lokalnych</title>
    <link href="http://plblog.danieljanus.pl/czyszczenie-dowiazan-lokalnych/"/>
    <updated>2011-01-31T00:00:00Z</updated>
    <content type="html">&lt;div&gt;&lt;p&gt;O jednej z nowych cech Clojure 1.2 dowiedziałem się dopiero niedawno, bo jej wprowadzenie przeszło właściwie bez echa. Może dlatego, że to optymalizacja niewidoczna na zewnątrz; jednak rozwiązuje ważny problem i dlatego warto mieć świadomość jej istnienia. Chodzi o &lt;em&gt;locals clearing&lt;/em&gt;, które tłumaczę na polski jako „czyszczenie lokalnych dowiązań”.&lt;/p&gt;&lt;p&gt;Wyobraźmy sobie taką sytuację: mamy do wykonania skomplikowane obliczenie. Możemy je podzielić na mniejsze kroki; na każdym etapie wyliczamy nowy pośredni wynik na podstawie tylko niewielu ostatnich kroków – jednego, może dwóch. Załóżmy jeszcze, że te pośrednie wyniki są dużych rozmiarów: powiedzmy setek megabajtów. To częsty przypadek, kiedy wykonuje się np. obliczenia na dużych, gęstych macierzach: najpierw normalizujemy każdy z wektorów naszej macierzy, potem ją transponujemy, następnie liczymy jej rozkład własny i odrzucamy część wektorów własnych.&lt;/p&gt;&lt;p&gt;W Javie wyglądałoby to tak:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs java"&gt;&lt;span class="hljs-keyword"&gt;void&lt;/span&gt; &lt;span class="hljs-title function_"&gt;calculate&lt;/span&gt;&lt;span class="hljs-params"&gt;(Matrix m)&lt;/span&gt; {
    &lt;span class="hljs-type"&gt;Matrix&lt;/span&gt; &lt;span class="hljs-variable"&gt;m1&lt;/span&gt; &lt;span class="hljs-operator"&gt;=&lt;/span&gt; step1(m);
    &lt;span class="hljs-type"&gt;Matrix&lt;/span&gt; &lt;span class="hljs-variable"&gt;m2&lt;/span&gt; &lt;span class="hljs-operator"&gt;=&lt;/span&gt; step2(m1);
    &lt;span class="hljs-comment"&gt;// i tak dalej, każdy krok odwołuje się do poprzedniego&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Jest to jednak prosta droga do pojawienia się &lt;code&gt;OutOfMemoryError&lt;/code&gt;, o ile nie dysponujemy wystarczającą ilością pamięci, aby pomieścić &lt;em&gt;wszystkie&lt;/em&gt; kroki. Gdy &lt;code&gt;m2&lt;/code&gt; jest obliczone, wartość &lt;code&gt;m1&lt;/code&gt; jest już niepotrzebna i w zasadzie można by zwolnić zajmowaną przez nią pamięć. Odśmiecacz jednak tego nie zrobi, dopóki wywołanie funkcji &lt;code&gt;calculate&lt;/code&gt; nie zakończy się, ponieważ do tej pamięci odwołuje się ramka na stosie wywołań.&lt;/p&gt;&lt;p&gt;Zilustrujmy to uruchamialnym przykładem:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs java"&gt;&lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title class_"&gt;foo&lt;/span&gt; {
    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-type"&gt;byte&lt;/span&gt;[] calculate(&lt;span class="hljs-type"&gt;byte&lt;/span&gt;[] lastStep) {
        &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-title class_"&gt;byte&lt;/span&gt;[&lt;span class="hljs-number"&gt;10485760&lt;/span&gt;];
    }

    &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;static&lt;/span&gt; &lt;span class="hljs-keyword"&gt;void&lt;/span&gt; &lt;span class="hljs-title function_"&gt;main&lt;/span&gt;&lt;span class="hljs-params"&gt;(String... args)&lt;/span&gt; {
        &lt;span class="hljs-type"&gt;byte&lt;/span&gt;[] a, b, c, d;
        a = calculate(&lt;span class="hljs-literal"&gt;null&lt;/span&gt;);
        b = calculate(a);
        c = calculate(b);
        d = calculate(c);
        System.out.println(&lt;span class="hljs-string"&gt;&amp;quot;OK&amp;quot;&lt;/span&gt;);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Funkcja &lt;code&gt;calculate&lt;/code&gt; emuluje skomplikowane obliczenia, alokując za każdym razem 10-megabajtowy blok pamięci. Jeśli skompilujemy ten program i uruchomimy go w środowisku z maksymalnym rozmiarem sterty ograniczonym do 30 MB (&lt;code&gt;java -Xmx30M foo&lt;/code&gt;), zobaczymy błąd braku pamięci.&lt;/p&gt;&lt;p&gt;Inaczej jest w Clojure. Odpowiednik powyższego programu wygląda tak:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;defn&lt;/span&gt; &lt;span class="hljs-title"&gt;calculate&lt;/span&gt; [last-step]
  (&lt;span class="hljs-name"&gt;make-array&lt;/span&gt; Byte/TYPE &lt;span class="hljs-number"&gt;10485760&lt;/span&gt;))

(&lt;span class="hljs-keyword"&gt;defn&lt;/span&gt; &lt;span class="hljs-title"&gt;-main&lt;/span&gt; [&amp;amp; args]
  (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;let&lt;/span&gt;&lt;/span&gt; [a (&lt;span class="hljs-name"&gt;calculate&lt;/span&gt; &lt;span class="hljs-literal"&gt;nil&lt;/span&gt;)
        b (&lt;span class="hljs-name"&gt;calculate&lt;/span&gt; a)
        c (&lt;span class="hljs-name"&gt;calculate&lt;/span&gt; b)
        d (&lt;span class="hljs-name"&gt;calculate&lt;/span&gt; c)]
    (&lt;span class="hljs-name"&gt;println&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;OK&amp;quot;&lt;/span&gt;)))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;W Clojure 1.2 i nowszych ten program wypisze &lt;code&gt;OK&lt;/code&gt;, nawet gdy będzie miał do dyspozycji tylko 30 MB pamięci.&lt;/p&gt;&lt;p&gt;Dlaczego to działa? Okazuje się, że kompilator Clojure przeprowadza statyczną analizę każdego kawałka kodu, w którym wartości są dowiązane do nazw (czyli każdego użycia formuły &lt;code&gt;let&lt;/code&gt;), i dla każdego dowiązania sprawdza, w którym miejscu jest ono ostatni raz używane, biorąc pod uwagę wszystkie możliwe ścieżki wykonania. Natychmiast po ostatnim użyciu emitowany jest bajtkod zerujący odpowiednią zmienną (ustawiający wskaźnik na &lt;code&gt;null&lt;/code&gt;). Innymi słowy, efekt jest taki, jakbyśmy w przykładowym kodzie w Javie wstawili instrukcję &lt;code&gt;a = null;&lt;/code&gt; zaraz po obliczeniu &lt;code&gt;b&lt;/code&gt; i analogicznie dalej.&lt;/p&gt;&lt;p&gt;O jednej z sytuacji, w których takie czyszczenie się przydaje, już powiedziałem. Innym częstym przypadkiem jest branie za punkt wyjścia długiej (być może nawet nieskończonej) leniwej sekwencji, która jest realizowana w kolejnych krokach obliczeń. Dzięki automatycznemu czyszczeniu odśmiecacz może pozbyć się pierwszych, niepotrzebnych już elementów zrealizowanej sekwencji, co umożliwia pisanie klarownego kodu bez troszczenia się o jego poprawność pamięciową.&lt;/p&gt;&lt;p&gt;Jak jednak &lt;a href="https://groups.google.com/d/msg/clojure/Xmu3pUMgiJk/Lx1Sq462h4wJ"&gt;napisał mi Rich Hickey&lt;/a&gt;, sama alokacja pamięci w momencie wiązania nazwy &lt;em&gt;nie jest&lt;/em&gt; uważana za użycie tej nazwy. Stąd jeśli nazwa nigdy nie zostanie użyta, to odpowiadająca jej wartość nie zostanie też odśmiecona aż do opuszczenia ciała bloku &lt;code&gt;let&lt;/code&gt;. Dlatego też nie mogłem pominąć w powyższym przykładzie argumentu &lt;code&gt;last-step&lt;/code&gt; funkcji &lt;code&gt;calculate&lt;/code&gt;.  Jednak taki przypadek nie pojawia się w praktyce: wszak jeśli wyliczamy jakiś wynik, to nie po to, by go do niczego nie użyć!&lt;/p&gt;&lt;/div&gt;</content>
  </entry>
  <entry>
    <id>tag:plblog.danieljanus.pl,2011-01-19:post:pokoloruj-sobie-europe</id>
    <title>Pokoloruj sobie Europę!</title>
    <link href="http://plblog.danieljanus.pl/pokoloruj-sobie-europe/"/>
    <updated>2011-01-19T00:00:00Z</updated>
    <content type="html">&lt;div&gt;&lt;p&gt;To jest druga część minicyklu zapoczątkowanego &lt;a href="http://plblog.danieljanus.pl/zippery-w-clojure"&gt;artykułem o zipperach&lt;/a&gt;. Temat ten zaprezentowałem również podczas wystąpienia na spotkaniu &lt;a href="http://warszawa.jug.pl"&gt;Warszawa JUG&lt;/a&gt;. Dostępne są &lt;a href="http://danieljanus.pl/slides/2010/wjug-clojure/"&gt;slajdy z tej prezentacji&lt;/a&gt; (&lt;em&gt;uwaga:&lt;/em&gt; slajdy mają postać oskryptowanego HTML; należy je oglądać w Firefoksie lub którejś przeglądarce WebKitowej). Można również obejrzeć &lt;a href="http://jaceklaskowski.blip.tv/file/4573404/"&gt;nagranie wideo&lt;/a&gt; wystąpienia, niestety ze słabą jakością dźwięku.&lt;/p&gt;&lt;h2 id="problem"&gt;Problem&lt;/h2&gt;&lt;p&gt;Jakiś czas temu zostałem poproszony o sporządzenie kilku mapek Europy pokolorowanych w różny sposób. Dostałem kilka zestawów danych, w których każdemu krajowi UE przypisana była jakaś liczba: im większa ta wartość, tym ciemniejszym odcieniem państwo miało być zaznaczone na mapce. Przykładowa pokolorowana mapka miała wyglądać tak:&lt;/p&gt;&lt;img src="/img/blog/europa.png"&gt;
&lt;p&gt;Rozpocząłem od ściągnięcia łatwo edytowalnej &lt;a href="http://commons.wikimedia.org/wiki/File:Blank_map_of_Europe.svg"&gt;mapy&lt;/a&gt; ze stron Wikimedia Commons, obliczyłem potrzebne intensywności kolorów dla pierwszego zestawu, uruchomiłem &lt;a href="http://www.inkscape.org"&gt;Inkscape&lt;/a&gt; i zabrałem się do kolorowania. Po półgodzinie żmudnego klikania zdałem sobie sprawę, że prościej i szybciej byłoby mi napisać programik w Clojure, który wygeneruje mapkę za mnie. Okazało się to zadaniem dość prostym: reszta tego postu będzie rekonstrukcją przedsięwziętych przeze mnie kroków.&lt;/p&gt;&lt;h2 id="svg"&gt;SVG&lt;/h2&gt;&lt;p&gt;Formatem źródłowego obrazka jest SVG. Wiedziałem, że to XML-owy otwarty format grafiki wektorowej, często widywałem na Wikipedii obrazy w tym formacie – jednak jego ręczna edycja to coś, z czym nie miałem do tej pory do czynienia. Szczęśliwie okazało się, że obrazek z mapkami ma prostą strukturę. Krzywa-obwiednia każdego kraju jest opisana elementem &lt;code&gt;path&lt;/code&gt;, wyglądającym mniej więcej tak:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs xml"&gt;&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;path&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;id&lt;/span&gt;=&lt;span class="hljs-string"&gt;&amp;quot;pl&amp;quot;&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;class&lt;/span&gt;=&lt;span class="hljs-string"&gt;&amp;quot;eu europe&amp;quot;&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;d&lt;/span&gt;=&lt;span class="hljs-string"&gt;&amp;quot;współrzędne kolejnych węzłów krzywej (bardzo dużo)&amp;quot;&lt;/span&gt; /&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Warto zwrócić uwagę na atrybut &lt;code&gt;id&lt;/code&gt; – jest to dwuliterowy skrót ISO-3166-1-ALPHA2 nazwy kraju. Na początku kodu obrazka znajduje się zresztą komentarz, który wyjaśnia użyte konwencje nazewnicze. Dysponowanie tak porządnie przygotowanymi danymi wejściowymi znakomicie ułatwiło mi pracę.&lt;/p&gt;&lt;p&gt;Jak się okazuje, SVG, podobnie jak HTML, &lt;a href="http://www.w3.org/TR/SVG/styling.html"&gt;używa stylów CSS&lt;/a&gt; do definiowania wyglądu elementów. Wszystko, co trzeba zrobić do nadania Polsce koloru czerwonego, to nadanie odpowiedniej wartości atrybutowi stylu &lt;code&gt;fill&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs xml"&gt;&lt;span class="hljs-tag"&gt;&amp;lt;&lt;span class="hljs-name"&gt;path&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;id&lt;/span&gt;=&lt;span class="hljs-string"&gt;&amp;quot;pl&amp;quot;&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;style&lt;/span&gt;=&lt;span class="hljs-string"&gt;&amp;quot;fill: #ff0000;&amp;quot;&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;class&lt;/span&gt;=&lt;span class="hljs-string"&gt;&amp;quot;eu europe&amp;quot;&lt;/span&gt;
  &lt;span class="hljs-attr"&gt;d&lt;/span&gt;=&lt;span class="hljs-string"&gt;&amp;quot;współrzędne kolejnych węzłów krzywej (bardzo dużo)&amp;quot;&lt;/span&gt; /&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Uzbrojeni w taką wiedzę, możemy rozpocząć pracę.&lt;/p&gt;&lt;h2 id="xml-w-clojure"&gt;XML w Clojure&lt;/h2&gt;&lt;p&gt;Podstawowym sposobem obsługi XML w Clojure jest wykorzystanie biblioteki &lt;code&gt;clojure.xml&lt;/code&gt;, pozwalającej na parsowanie XML-a (na zasadzie DOM, tzn. do struktury w pamięci) oraz serializację, tzn. działanie w odwrotną stronę. Uruchommy więc REPL i zacznijmy od wczytania naszej mapki i sparsowania jej:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;&amp;gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;use&lt;/span&gt;&lt;/span&gt; &amp;#x27;clojure.xml)
&lt;span class="hljs-literal"&gt;nil&lt;/span&gt;
&amp;gt; (&lt;span class="hljs-keyword"&gt;def&lt;/span&gt; &lt;span class="hljs-title"&gt;m&lt;/span&gt; (&lt;span class="hljs-name"&gt;parse&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;/home/nathell/eur/Blank_map_of_Europe.svg&amp;quot;&lt;/span&gt;))
[...dłuuugie oczekiwanie...]
Unexpected end of file from server
  [Thrown class java.net.SocketException]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Zaraz! Jaki znowu &lt;code&gt;SocketException&lt;/code&gt;? Firefox wyświetla tę mapkę poprawnie, Chromium też, WTF? W tak świetnym języku, jakim jest Clojure, wszystko powinno działać od ręki?!&lt;/p&gt;&lt;p&gt;Cóż, język jest tak dobry, jak dobre są jego biblioteki – a jeśli chodzi o Clojure, można to rozwinąć: biblioteki Clojure są tak dobre, jak dobre są biblioteki Javy, których używają pod spodem. W tym wypadku natrafiliśmy na cechę standardowego (z pakietu &lt;code&gt;javax.xml&lt;/code&gt;) parsera XML w Javie. Jest on restrykcyjny i stara się odrzucać dokumenty, które nie są poprawne (w sensie &lt;em&gt;valid&lt;/em&gt;, a nie tylko &lt;em&gt;well-formed&lt;/em&gt;). Jeśli tylko dany plik zawiera deklarację &lt;code&gt;DOCTYPE&lt;/code&gt;, to parser javowy, a za nim &lt;code&gt;clojure.xml/parse&lt;/code&gt;, próbuje ściągnąć z podanego adresu schemat DTD i zwalidować dokument według tego schematu. Jest to działanie z wielu względów niepożądane, zwłaszcza z punktu widzenia &lt;a href="http://www.w3.org"&gt;World Wide Web Consortium&lt;/a&gt;, bo to na serwerach tej organizacji przechowywane są schematy opisujące sieciowe standardy. Łatwo sobie wyobrazić, jak duży ruch sieciowy generują nadgorliwe parsery: traktuje o tym &lt;a href="http://www.w3.org/blog/systeam?cat=68"&gt;post na blogu W3C&lt;/a&gt;. Zapewne wielu programistów Javy zetknęło się kiedyś z tym problemem. Jest kilka rozwiązań; użyjemy najprostszego z nich, czyli po prostu... ręcznie usuniemy przeszkadzającą deklarację &lt;code&gt;DOCTYPE&lt;/code&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;&amp;gt; (&lt;span class="hljs-keyword"&gt;def&lt;/span&gt; &lt;span class="hljs-title"&gt;m&lt;/span&gt; (&lt;span class="hljs-name"&gt;parse&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;/home/nathell/eur/bm.svg&amp;quot;&lt;/span&gt;))
#&amp;#x27;user/m
&amp;gt; m
[...wiele ekranów liczb...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;No, tym razem udało się wczytać. Obejrzenie sparsowanej struktury jest utrudnione z uwagi na jej wielkość (można było się tego spodziewać: plik SVG waży ponad 0,5 MB!), ale z samego początku tego, co nam wypluwa REPL, widzimy, że jest to mapa. Zobaczmy, z jakich kluczy się składa:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;&amp;gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;keys&lt;/span&gt;&lt;/span&gt; m)
(&lt;span class="hljs-symbol"&gt;:tag&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:attrs&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:content&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Jest to więc mapa o trzech polach: z nazw (albo z dokumentacji) można się domyślić, co zawierają. Pole &lt;code&gt;:tag&lt;/code&gt; to nazwa elementu XML, &lt;code&gt;:attrs&lt;/code&gt; jest mapą atrybutów tego elementu, a &lt;code&gt;:content&lt;/code&gt; – sekwencją jego podelementów, z których każdy jest znów reprezentowany przez mapę o takiej samej strukturze (względnie przez napis, jeśli jest to element napisowy):&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;&amp;gt; (&lt;span class="hljs-symbol"&gt;:tag&lt;/span&gt; m)
&lt;span class="hljs-symbol"&gt;:svg&lt;/span&gt;
&amp;gt; (&lt;span class="hljs-symbol"&gt;:attrs&lt;/span&gt; m)
{&lt;span class="hljs-symbol"&gt;:xmlns&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;http://www.w3.org/2000/svg&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:width&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;680&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:height&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;520&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:viewBox&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;1754 161 9938 7945&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:version&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;1.0&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:id&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;svg2&amp;quot;&lt;/span&gt;}
&amp;gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;count&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-symbol"&gt;:content&lt;/span&gt; m))
&lt;span class="hljs-number"&gt;68&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Tak dla wprawki, spróbujmy zapisać sparsowaną reprezentację z powrotem jako XML. Funkcja &lt;code&gt;emit&lt;/code&gt; powinna być w stanie to zrobić, ale wypisujekl ona XML na standardowe wyjście. Możemy użyć makra &lt;code&gt;with-out-writer&lt;/code&gt; z pakietu &lt;code&gt;clojure.contrib.io&lt;/code&gt;, aby zrzucić XML-a do pliku:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;&amp;gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;use&lt;/span&gt;&lt;/span&gt; &amp;#x27;clojure.contrib.io)
&lt;span class="hljs-literal"&gt;nil&lt;/span&gt;
&amp;gt; (&lt;span class="hljs-name"&gt;with-out-writer&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;/tmp/a.xml&amp;quot;&lt;/span&gt; (&lt;span class="hljs-name"&gt;emit&lt;/span&gt; m))
&lt;span class="hljs-literal"&gt;nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Próbujemy otworzyć wynikowy obrazek w Firefoksie i...&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Błąd parsowania XML: nieprawidłowo sformowany
Obszar: file:///tmp/a.xml
Numer linii: 15, kolumna 44: Updated to reflect dissolution of Serbia &amp;amp; Montenegro: http://commons.wikimedia.org/wiki/User:Zirland
----------------------------------------------------------------------^
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Argh! Wygląda na to, że znaleźliśmy błąd w &lt;code&gt;clojure.xml&lt;/code&gt;: elementy zawierające ampersandy nie są poprawnie serializowane, przez co późniejsze parsowanie się nie powodzi. Zgłosimy ten błąd później, a teraz usuńmy znowu doraźnie wskazaną przez Firefoksa linijkę (możemy to bezpiecznie zrobić, bo to tylko komentarz). Jeśli teraz powtórzymy nasze dotychczasowe kroki, zobaczymy, że wygenerowany XML jest wreszcie poprawny.&lt;/p&gt;&lt;h2 id="kolorujemy-polskę"&gt;Kolorujemy Polskę&lt;/h2&gt;&lt;p&gt;Widzieliśmy wcześniej, że główny element XML w naszym pliku zawiera 68 podelementów. Zobaczmy, jakie one są – wystarczą nam tagi:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;&amp;gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;map&lt;/span&gt;&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:tag&lt;/span&gt; (&lt;span class="hljs-symbol"&gt;:content&lt;/span&gt; m))
(&lt;span class="hljs-symbol"&gt;:title&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:desc&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:defs&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:rect&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:g&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:g&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Jak dotąd jest świetnie. Wygląda na to, że wszystkie opisy krajów są bezpośrednio zawarte w głównym elemencie jako podelementy. Spróbujmy znaleźć Polskę:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;&amp;gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;count&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;filter&lt;/span&gt;&lt;/span&gt; #(&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;and&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;=&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-symbol"&gt;:tag&lt;/span&gt; %) &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt;)
                       (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;=&lt;/span&gt;&lt;/span&gt; ((&lt;span class="hljs-symbol"&gt;:attrs&lt;/span&gt; %) &lt;span class="hljs-symbol"&gt;:id&lt;/span&gt;) &lt;span class="hljs-string"&gt;&amp;quot;pl&amp;quot;&lt;/span&gt;))
                 (&lt;span class="hljs-symbol"&gt;:content&lt;/span&gt; m)))
&lt;span class="hljs-number"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(Ten kawałek kodu wybiera z listy podelementów &lt;code&gt;m&lt;/code&gt; takie, których tag to &lt;code&gt;path&lt;/code&gt;, a wartość atrybutu &lt;code&gt;id&lt;/code&gt; to &lt;code&gt;"pl"&lt;/code&gt; i zwraca długość takiej podlisty). Spróbujmy teraz dodać do tego elementu atrybut &lt;code&gt;style&lt;/code&gt;, zgodnie z tym, co powiedzieliśmy wcześniej. Ze względu na niezmienność struktur danych musimy zdefiniować nowy nadrzędny element, który będzie taki sam, jak &lt;code&gt;m&lt;/code&gt;, z tym, że odpowiedniemu podelementowi ustawimy styl:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;&amp;gt; (&lt;span class="hljs-keyword"&gt;def&lt;/span&gt; &lt;span class="hljs-title"&gt;m2&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;assoc&lt;/span&gt;&lt;/span&gt; m
                &lt;span class="hljs-symbol"&gt;:content&lt;/span&gt;
                (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;map&lt;/span&gt;&lt;/span&gt; #(&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;if&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;and&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;=&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-symbol"&gt;:tag&lt;/span&gt; %) &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt;)
                               (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;=&lt;/span&gt;&lt;/span&gt; ((&lt;span class="hljs-symbol"&gt;:attrs&lt;/span&gt; %) &lt;span class="hljs-symbol"&gt;:id&lt;/span&gt;) &lt;span class="hljs-string"&gt;&amp;quot;pl&amp;quot;&lt;/span&gt;))
                        (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;assoc&lt;/span&gt;&lt;/span&gt; % &lt;span class="hljs-symbol"&gt;:attrs&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;assoc&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-symbol"&gt;:attrs&lt;/span&gt; %) &lt;span class="hljs-symbol"&gt;:style&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;fill: #ff0000;&amp;quot;&lt;/span&gt;))
                        %)
                     (&lt;span class="hljs-symbol"&gt;:content&lt;/span&gt; m))))
#&amp;#x27;user/m&lt;span class="hljs-number"&gt;2&lt;/span&gt;
&amp;gt; (&lt;span class="hljs-name"&gt;with-out-writer&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;/tmp/a.svg&amp;quot;&lt;/span&gt; (&lt;span class="hljs-name"&gt;emit&lt;/span&gt; m2))
&lt;span class="hljs-literal"&gt;nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Otwieramy utworzony plik i widzimy mapkę z Polską zaznaczoną na czerwono. Hura!&lt;/p&gt;&lt;h2 id="generalizacja"&gt;Generalizacja&lt;/h2&gt;&lt;p&gt;Uogólnimy trochę nasz kod. Napiszemy funkcję, która pokoloruje jedno państwo, dostając na wejściu element &lt;code&gt;path&lt;/code&gt; (podelement &lt;code&gt;svg&lt;/code&gt;):&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;defn&lt;/span&gt; &lt;span class="hljs-title"&gt;color-state&lt;/span&gt;
  [{&lt;span class="hljs-symbol"&gt;:keys&lt;/span&gt; [tag attrs] &lt;span class="hljs-symbol"&gt;:as&lt;/span&gt; element} colorize-fn]
  (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;let&lt;/span&gt;&lt;/span&gt; [state (&lt;span class="hljs-symbol"&gt;:id&lt;/span&gt; attrs)]
    (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;if-let&lt;/span&gt;&lt;/span&gt; [color (&lt;span class="hljs-name"&gt;colorize-fn&lt;/span&gt; state)]
      (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;assoc&lt;/span&gt;&lt;/span&gt; element &lt;span class="hljs-symbol"&gt;:attrs&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;assoc&lt;/span&gt;&lt;/span&gt; attrs &lt;span class="hljs-symbol"&gt;:style&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;str&lt;/span&gt;&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;fill:&amp;quot;&lt;/span&gt; color)))
      element)))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Jest to funkcja bardzo podobna do tej anonimowej, użytej powyżej w wywołaniu &lt;code&gt;map&lt;/code&gt;, ale różni się w kilku szczegółach. Po pierwsze, bierze dwa argumenty. Jednym jest, jak powiedzieliśmy, element XML-owy (od razu rozbity na &lt;code&gt;tag&lt;/code&gt; i &lt;code&gt;attrs&lt;/code&gt;: więcej o takiej notacji, zwanej &lt;em&gt;destructuring&lt;/em&gt; („dzielenie struktury”) można przeczytać w &lt;a href="http://clojure.org/special_forms"&gt;odpowiedniej części dokumentacji Clojure&lt;/a&gt;), a drugim... funkcja, która dostanie jako argument dwuliterowy kod państwa i zwróci HTML-owy opis jego koloru (lub &lt;code&gt;nil&lt;/code&gt;, jeśli kolor dla tego państwa nie jest określony – &lt;code&gt;color-state&lt;/code&gt; poradzi sobie z tym i zwróci element w stanie nienaruszonym).&lt;/p&gt;&lt;p&gt;Mając &lt;code&gt;color-state&lt;/code&gt;, możemy łatwo napisać funkcję wyższego poziomu, która w jednym kroku przetworzy i zapisze XML:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;defn&lt;/span&gt; &lt;span class="hljs-title"&gt;save-color-map&lt;/span&gt;
  [svg colorize-fn outfile]
  (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;let&lt;/span&gt;&lt;/span&gt; [colored-map (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;assoc&lt;/span&gt;&lt;/span&gt; svg &lt;span class="hljs-symbol"&gt;:content&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;map&lt;/span&gt;&lt;/span&gt; #(&lt;span class="hljs-name"&gt;color-state&lt;/span&gt; % colorize-fn) (&lt;span class="hljs-symbol"&gt;:content&lt;/span&gt; svg)))]
    (&lt;span class="hljs-name"&gt;with-out-writer&lt;/span&gt; out
      (&lt;span class="hljs-name"&gt;emit&lt;/span&gt; colored-map))))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Testujemy:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;&amp;gt; (&lt;span class="hljs-name"&gt;save-color-map&lt;/span&gt; m {&lt;span class="hljs-string"&gt;&amp;quot;pl&amp;quot;&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;#00ff00&amp;quot;&lt;/span&gt;} &lt;span class="hljs-string"&gt;&amp;quot;/tmp/a.svg&amp;quot;&lt;/span&gt;)
&lt;span class="hljs-literal"&gt;nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Tym razem Polska jest zielona (jako argumentu &lt;code&gt;color-state&lt;/code&gt; użyliśmy mapy kraj→kolor, bo mapy są w Clojure wołalne jak funkcje). Spróbujmy dorzucić niebieskie Niemcy:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;&amp;gt; (&lt;span class="hljs-name"&gt;save-color-map&lt;/span&gt; m {&lt;span class="hljs-string"&gt;&amp;quot;pl&amp;quot;&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;#00ff00&amp;quot;&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;de&amp;quot;&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;#0000ff&amp;quot;&lt;/span&gt;} &lt;span class="hljs-string"&gt;&amp;quot;/tmp/a.svg&amp;quot;&lt;/span&gt;)
&lt;span class="hljs-literal"&gt;nil&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Działa!&lt;/p&gt;&lt;h2 id="problem-z-wielką-brytanią"&gt;Problem z Wielką Brytanią&lt;/h2&gt;&lt;p&gt;Zachęceni sukcesem, próbujemy kolorować różne kraje. Dla większości działa, ale Wielka Brytania jak była szara, tak jest, niezależnie od tego, czy podamy jej kod jako „uk”, czy jako „gb”. Zaglądamy jeszcze raz do źródła naszego obrazka. Komentarze na początku jak zwykle okazują się pomocne:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Certain countries are further subdivided the United Kingdom has gb-gbn for Great Britain and gb-nir for Northern Ireland. Russia is divided into ru-kgd for the Kaliningrad Oblast and ru-main for the Main body of Russia. There is the additional grouping #xb for the „British Islands” (the UK with its Crown Dependencies - Jersey, Guernsey and the Isle of Man)&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Może więc trzeba podać „gb-gbn” i „gb-nir”, zamiast samego „gb”? Próbujemy i... też nie działa. Po chwili namysłu: no jasne! Nasze początkowe założenie, że &lt;em&gt;wszystkie&lt;/em&gt; definicje krajów są bezpośrednimi podelementami &lt;code&gt;path&lt;/code&gt;, okazuje się fałszywe. Trzeba to naprawić.&lt;/p&gt;&lt;p&gt;Dotychczas robiliśmy „płaską” transformację drzewa SVG: zamienialiśmy wszystkie podelementy elementu głównego i nic głębiej. Trzeba by zmienić wszystkie elementy &lt;code&gt;path&lt;/code&gt; (i &lt;code&gt;g&lt;/code&gt;, jeśli chcemy kolorować grupy takie jak Wielka Brytania), niezależnie od tego, jak głęboko siedzą w drzewie. Z pomocą przychodzi funkcja &lt;code&gt;map-zipper&lt;/code&gt; z &lt;a href="http://plblog.danieljanus.pl/zippery-w-clojure"&gt;poprzedniego odcinka&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Zważywszy, że funkcja &lt;code&gt;clojure.xml/zip-xml&lt;/code&gt; zwraca zipper działający na drzewach XML-owych, przepisujemy &lt;code&gt;save-color-map&lt;/code&gt; jako:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;defn&lt;/span&gt; &lt;span class="hljs-title"&gt;save-color-map&lt;/span&gt;
  [svg colorize-fn outfile]
  (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;let&lt;/span&gt;&lt;/span&gt; [colored-map (&lt;span class="hljs-name"&gt;map-zipper&lt;/span&gt; #(&lt;span class="hljs-name"&gt;color-state&lt;/span&gt; % colorize-fn) (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;fn&lt;/span&gt;&lt;/span&gt; [x] (#{&lt;span class="hljs-symbol"&gt;:g&lt;/span&gt; &lt;span class="hljs-symbol"&gt;:path&lt;/span&gt;} (&lt;span class="hljs-symbol"&gt;:tag&lt;/span&gt; x))) (&lt;span class="hljs-name"&gt;zip/xml-zip&lt;/span&gt; svg))]
    (&lt;span class="hljs-name"&gt;with-out-writer&lt;/span&gt; out
      (&lt;span class="hljs-name"&gt;emit&lt;/span&gt; colored-map))))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Tym razem Wielka Brytania już daje się pomalować.&lt;/p&gt;&lt;h2 id="koloryzatory"&gt;Koloryzatory&lt;/h2&gt;&lt;p&gt;Mamy już zautomatyzowany sam proces kolorowania, ale tłumaczenie konkretnych wartości liczbowych na RGB jest żmudne. W ostatniej części tego artykułu zobaczymy, jak je ułatwić: stworzymy funkcję zwracającą &lt;em&gt;koloryzator&lt;/em&gt;, czyli funkcję nadającą się do przekazania jako argument do &lt;code&gt;color-state&lt;/code&gt; i &lt;code&gt;save-color-map&lt;/code&gt; (dotąd używaliśmy w tym celu map).&lt;/p&gt;&lt;p&gt;Zaczniemy od napisania funkcji tłumaczącej trójkę liczb na HTML-owy zapis RGB, będzie nam bowiem łatwiej pracować z liczbami całkowitymi niż napisami:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;defn&lt;/span&gt; &lt;span class="hljs-title"&gt;htmlize-color&lt;/span&gt;
  [[r g b]]
  (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;format&lt;/span&gt;&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;#%02x%02x%02x&amp;quot;&lt;/span&gt; r g b))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Wstawmy jeszcze wywołanie &lt;code&gt;htmlize-color&lt;/code&gt; w odpowiednim miejscu w &lt;code&gt;color-state&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;defn&lt;/span&gt; &lt;span class="hljs-title"&gt;color-state&lt;/span&gt;
  [{&lt;span class="hljs-symbol"&gt;:keys&lt;/span&gt; [tag attrs] &lt;span class="hljs-symbol"&gt;:as&lt;/span&gt; element} colorize-fn]
  (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;let&lt;/span&gt;&lt;/span&gt; [state (&lt;span class="hljs-symbol"&gt;:id&lt;/span&gt; attrs)]
    (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;if-let&lt;/span&gt;&lt;/span&gt; [color (&lt;span class="hljs-name"&gt;colorize-fn&lt;/span&gt; state)]
      (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;assoc&lt;/span&gt;&lt;/span&gt; element &lt;span class="hljs-symbol"&gt;:attrs&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;assoc&lt;/span&gt;&lt;/span&gt; attrs &lt;span class="hljs-symbol"&gt;:style&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;str&lt;/span&gt;&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;fill:&amp;quot;&lt;/span&gt; (&lt;span class="hljs-name"&gt;htmlize-color&lt;/span&gt; color))))
      element)))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Wyobraźmy sobie teraz, że mamy tabelkę z wartościami liczbowymi dla państw, np. taką:&lt;/p&gt;&lt;table class="entry"&gt;
&lt;tr class="header"&gt;&lt;th&gt;Państwo&lt;/th&gt;&lt;th&gt;Wartość&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Polska&lt;/td&gt;&lt;td class="center"&gt;20&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Niemcy&lt;/td&gt;&lt;td class="center"&gt;15&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Holandia&lt;/td&gt;&lt;td class="center"&gt;30&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;Chcemy mieć funkcję, która przypisze każdemu krajowi kolor tym intensywniejszy, im większa wartość, przy czym intensywność ma być proporcjonalna do wartości. Dla większej ogólności załóżmy, że mamy daną parę kolorów, c1 i c2, i dla każdej ze składowych R, G i B danemu państwu przypisujemy taką wartość tej składowej, jaka jest odległość rozważanej wartości od najmniejszej wartości w naszym zestawie danych (oczywiście znormalizowana biorąc pod uwagę długości odpowiednich przedziałów).&lt;/p&gt;&lt;p&gt;Brzmi to zagmatwanie, ale mam nadzieję, że na przykładzie będzie widać lepiej. Tak wygląda implementacja clojurowa powyższej funkcji:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;defn&lt;/span&gt; &lt;span class="hljs-title"&gt;make-colorizer&lt;/span&gt;
  [dataset ranges]
  (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;let&lt;/span&gt;&lt;/span&gt; [minv (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;apply&lt;/span&gt;&lt;/span&gt; min (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;vals&lt;/span&gt;&lt;/span&gt; dataset))
        maxv (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;apply&lt;/span&gt;&lt;/span&gt; max (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;vals&lt;/span&gt;&lt;/span&gt; dataset))
        progress (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;map&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;fn&lt;/span&gt;&lt;/span&gt; [[min-col max-col]] (/ (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;-&lt;/span&gt;&lt;/span&gt; max-col min-col) (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;-&lt;/span&gt;&lt;/span&gt; maxv minv))) ranges)]
    (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;into&lt;/span&gt;&lt;/span&gt; {}
          (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;map&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;fn&lt;/span&gt;&lt;/span&gt; [[k v]] [(&lt;span class="hljs-name"&gt;.toLowerCase&lt;/span&gt; k) (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;map&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;fn&lt;/span&gt;&lt;/span&gt; [progress [min-color _]] (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;int&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;+&lt;/span&gt;&lt;/span&gt; min-color (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;*&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;-&lt;/span&gt;&lt;/span&gt; v minv) progress)))) progress ranges)])
               dataset))))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Zobaczmy, jak działa na naszych przykładowych danych:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;&amp;gt; (&lt;span class="hljs-name"&gt;make-colorizer&lt;/span&gt; {&lt;span class="hljs-string"&gt;&amp;quot;pl&amp;quot;&lt;/span&gt; &lt;span class="hljs-number"&gt;20&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;de&amp;quot;&lt;/span&gt; &lt;span class="hljs-number"&gt;15&lt;/span&gt;&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;nl&amp;quot;&lt;/span&gt; &lt;span class="hljs-number"&gt;30&lt;/span&gt;} [[&lt;span class="hljs-number"&gt;0&lt;/span&gt; &lt;span class="hljs-number"&gt;255&lt;/span&gt;] [&lt;span class="hljs-number"&gt;0&lt;/span&gt; &lt;span class="hljs-number"&gt;0&lt;/span&gt;] [&lt;span class="hljs-number"&gt;0&lt;/span&gt; &lt;span class="hljs-number"&gt;0&lt;/span&gt;]])
{&lt;span class="hljs-string"&gt;&amp;quot;pl&amp;quot;&lt;/span&gt; (&lt;span class="hljs-number"&gt;85&lt;/span&gt; &lt;span class="hljs-number"&gt;0&lt;/span&gt; &lt;span class="hljs-number"&gt;0&lt;/span&gt;)&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;de&amp;quot;&lt;/span&gt; (&lt;span class="hljs-number"&gt;0&lt;/span&gt; &lt;span class="hljs-number"&gt;0&lt;/span&gt; &lt;span class="hljs-number"&gt;0&lt;/span&gt;)&lt;span class="hljs-punctuation"&gt;,&lt;/span&gt; &lt;span class="hljs-string"&gt;&amp;quot;nl&amp;quot;&lt;/span&gt; (&lt;span class="hljs-number"&gt;255&lt;/span&gt; &lt;span class="hljs-number"&gt;0&lt;/span&gt; &lt;span class="hljs-number"&gt;0&lt;/span&gt;)}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Drugi argument oznacza, że składowa czerwona koloru ma się zmieniać od 0 do 255, a zielona i niebieska mają pozostać stałe (na poziomie 0).&lt;/p&gt;&lt;p&gt;Tak jak żądaliśmy, Niemcy wychodzą najciemniejsze (bo mają najmniejszą wartość), Holandia najjaśniejsza (bo ma największą wartość), a Polska jest w 1/3 jasności między Niemcami a Holandią (bo 20 jest w 1/3 drogi między 15 a 30).&lt;/p&gt;&lt;h2 id="podsumowanie"&gt;Podsumowanie&lt;/h2&gt;&lt;p&gt;Aplikację stworzoną w tym artykule można dalej rozwijać w wielu kierunkach. Można stworzyć jej wersję sieciową (jak się do tego zabrać -- opowiedziałem w skrócie na spotkaniu Warszawa JUG). Można pisać różne koloryzatory, np. dyskretny (stałe wartości dla różnych podprzedziałów wejściowego przedziału danych) albo „temperaturowy” (płynnie przechodzący od błękitu do czerwieni przez biel: trzeba w tym celu przejść przez przestrzeń kolorów HSV).&lt;/p&gt;&lt;p&gt;A jaki Wy macie pomysł na rozwinięcie przykładu? Zapraszam do komentowania i eksperymentowania! Dla tych, którym się nie chce przeklejać kodu do REPL, kompletne źródło działające z Leiningenem umieszczam na &lt;a href="https://github.com/nathell/color-europe"&gt;GitHub&lt;/a&gt;. Forki będą bardzo mile widziane.&lt;/p&gt;&lt;/div&gt;</content>
  </entry>
  <entry>
    <id>tag:plblog.danieljanus.pl,2011-01-13:post:protokoly-i-git-bisect</id>
    <title>Pierwsze użycie: protokoły i git-bisect</title>
    <link href="http://plblog.danieljanus.pl/protokoly-i-git-bisect/"/>
    <updated>2011-01-13T00:00:00Z</updated>
    <content type="html">&lt;div&gt;&lt;p&gt;Lubię rozwiązywać problemy przy użyciu narzędzi, których dotychczas nie znałem albo znałem tylko teoretycznie, na zasadzie „wiem, że istnieje coś takiego i do czego z grubsza służy”. Jeszcze przyjemniej jest, kiedy uzyskane rozwiązanie okazuje się czytelniejsze, zrozumialsze, szybsze albo pod innymi względami lepsze niż wersja używająca tylko dotychczasowego „arsenału”. W tych dniach zdarzyło mi się tego doświadczyć dwukrotnie, gdy pracowałem nad &lt;a href="http://fablo.pl"&gt;Fablo&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Fablo to napisany w &lt;a href="http://clojure.org"&gt;Clojure&lt;/a&gt; silnik wyszukiwarki dla polskich sklepów internetowych: zna odmianę polskich wyrazów, obsługuje literówki i błędy ortograficzne. Radzi sobie też z wyrazami wpisanymi bez polskich znaków, a więc wie, że „papryka zolta” to to samo, co „papryka żółta”. Działa to na ogół zupełnie dobrze, ale wciąż ulepszamy wyszukiwarkę i eliminujemy błędy polegające na tym, że na jakieś zapytanie nie wyszukują się niektóre produkty, choć powinny. Tym razem błąd brzmiał: zapytanie „sledzie” zwraca inne wyniki niż „śledzie”.&lt;/p&gt;&lt;p&gt;Analiza problemu ujawniła, że algorytm odpowiedzialny za „polszczenie” zapytania powinien być stosowany szerzej. Mamy dwie implementacje pewnej struktury danych używanej w tym algorytmie: jedna z nich jest czysto funkcyjna i zbudowana z rdzennie clojurowego tworzywa – map i wektorów; druga zaś jest ukryta za fasadą używanej przez nas biblioteki Javowej o prostym interfejsie, a więc explicite do tej pory z niej nie korzystaliśmy. Algorytm polszczenia był uruchamiany tylko na pierwszej implementacji (co wystarczy w wielu przypadkach, ale nie zawsze), a powinien być na obu.&lt;/p&gt;&lt;p&gt;Koncepcyjnie te dwie implementacje są podobne, ale mają zupełnie różne API – zrazu wydawało mi się więc, że będę musiał pisać drugą wersję funkcji realizującej „polszczenie”. To jednak nie byłoby optymalne: duplikacja kodu oznacza dwa razy więcej okazji do popełnienia błędu i konieczność pamiętania o wprowadzeniu zmian każdorazowo w obu miejscach. Z pomocą przyszły &lt;a href="http://clojure.org/Protocols"&gt;protokoły&lt;/a&gt; – nowy element języka wprowadzony w Clojure 1.2.&lt;/p&gt;&lt;p&gt;Protokół jest czymś bardzo zbliżonym do znanego z Javy interfejsu: to zestaw deklaracji nazwanych funkcji i ich argumentów, jednak bez implementacji. Implementacje definiuje się dla konkretnych typów. Mogą to być zarówno typy definiowane w Clojure (przy użyciu konstrukcji &lt;code&gt;deftype&lt;/code&gt;), jak i istniejące klasy Javowe. Można więc zadeklarować w protokole jakąś operację (nazwijmy ją blabalizacją), po czym zdefiniować, co oznacza blabalizowanie liczb, a co napisów. Efekt jest taki, jak gdybyśmy w Javie jakoś zmusili klasy &lt;code&gt;java.lang.Integer&lt;/code&gt; i &lt;code&gt;java.lang.String&lt;/code&gt; do implementowania interfejsu &lt;code&gt;Blabalize&lt;/code&gt;; możemy wołać odpowiednią funkcję bezpośrednio na obiektach odpowiednich klas, bez żadnych wrapperów!&lt;/p&gt;&lt;p&gt;Zacząłem więc od zdefiniowania protokołu, opisującego dwie operacje, jakich można dokonywać na mojej strukturze danych. Potem przerobiłem implementację algorytmu „polszczącego” zapytania tak, aby korzystała tylko z tych dwóch operacji, co uniezależniło ją od implementacji „pod spodem”. Napisanie Clojurowej implementacji protokołu było proste; trudniejsze okazało się odpowiednie skorzystanie z biblioteki Javowej, ale i to udało się zrobić w miarę szybko.&lt;/p&gt;&lt;p&gt;Efekt: brak duplikacji kodu i tylko nieznacznie zmodyfikowana dotychczasowa wersja. A przy tym udało się nagiąć bibliotekę Javową do robienia rzeczy, do których nie była zaprojektowana, bez ingerencji w jej kod. Clojure nie przestaje mnie zadziwiać.&lt;/p&gt;&lt;p&gt;Innym razem zauważyłem, że od kilku commitów przy próbie uruchomienia testów jednostkowych i regresji w logach testowania pojawia się komunikat, którego zdecydowanie nie powinno tam być. Mógłbym zabrać się do tego testując okolice pojawienia się komunikatu w REPL, ale tym razem postanowiłem zrobić to inaczej. Ponieważ używamy &lt;a href="http://git-scm.com/"&gt;Git&lt;/a&gt; do kontroli wersji, wykorzystałem narzędzie &lt;a href="http://www.kernel.org/pub/software/scm/git/docs/git-bisect.html"&gt;git-bisect&lt;/a&gt;, aby znaleźć commit wprowadzający komunikat. Potrzebuje ono do działania trzech rzeczy: identyfikatorów commitu „dobrego” i „złego” oraz skryptu, który odpowiada na pytanie, czy dany commit jest „dobry” czy „zły” (zwracającego kod wyjścia odpowiednio 0 albo 1). Skrypt wyglądał mniej więcej następująco:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs bash"&gt;cake clean
cake deps
cake proto
&lt;span class="hljs-built_in"&gt;sleep&lt;/span&gt; 10
cake release
&lt;span class="hljs-built_in"&gt;rm&lt;/span&gt; *.&lt;span class="hljs-built_in"&gt;log&lt;/span&gt;
make &lt;span class="hljs-built_in"&gt;test&lt;/span&gt;
&lt;span class="hljs-keyword"&gt;if&lt;/span&gt; grep -q &lt;span class="hljs-string"&gt;&amp;quot;PODEJRZANY_KOMUNIKAT&amp;quot;&lt;/span&gt; fablo*&lt;span class="hljs-built_in"&gt;log&lt;/span&gt;; &lt;span class="hljs-keyword"&gt;then&lt;/span&gt;
  &lt;span class="hljs-built_in"&gt;exit&lt;/span&gt; 1
&lt;span class="hljs-keyword"&gt;else&lt;/span&gt;
  &lt;span class="hljs-built_in"&gt;exit&lt;/span&gt; 0
&lt;span class="hljs-keyword"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(&lt;a href="http://github.com/ninjudd/cake"&gt;Cake&lt;/a&gt; jest narzędziem, którego używamy do budowania oprogramowania; 10-sekundowa przerwa między &lt;code&gt;cake proto&lt;/code&gt; a &lt;code&gt;cake release&lt;/code&gt; jest remedium na błąd w cake.)&lt;/p&gt;&lt;p&gt;Potem wystarczyło tylko:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs bash"&gt;$ git bisect start HEAD dobry_commit --
$ git bisect run check.sh
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;i już wiedziałem, w którym miejscu się zepsuło, a stąd było już niedaleko do znalezienia przyczyny problemu.&lt;/p&gt;&lt;/div&gt;</content>
  </entry>
  <entry>
    <id>tag:plblog.danieljanus.pl,2010-11-25:post:zippery-w-clojure</id>
    <title>Zippery w Clojure</title>
    <link href="http://plblog.danieljanus.pl/zippery-w-clojure/"/>
    <updated>2010-11-25T00:00:00Z</updated>
    <content type="html">&lt;div&gt;&lt;p&gt;Niniejszy artykuł jest pierwszym z dwuczęściowego minicyklu, stanowiącego demonstrację wykorzystania Clojure w praktyce do prostego, acz nietrywialnego zadania, na które natknąłem się w codziennej pracy. Dzisiejsza część może wydać się mało interesująca i mocno teoretyczna, ale mam nadzieję, że następny odcinek pokaże, jak można ją ciekawie wykorzystać (na razie nie zdradzę, jakie to wykorzystanie).&lt;/p&gt;&lt;p&gt;Planuję, że takie artykuły lub cykle zdominują Clojurową część tego bloga – będę się tu dzielił rozwiązaniami praktycznych problemów, na jakie natrafiam. Nie będzie tu artykułów w stylu „hej, jaki fajny nowy framework XYZ, napiszmy w nim Hello World przy wykorzystaniu technologii ABC!”, chyba że XYZ lub ABC będą przydatnymi narzędziami do rozwiązania problemu z życia wziętego. Bo takie rozwiązywanie w Clojure daje dużo radości: nie przypadkiem taki właśnie jest tytuł &lt;a href="http://joyofclojure.com/"&gt;książki M. Fogusa i C. Housera&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;A dziś opowiem o ciekawej strukturze danych, jaką są &lt;em&gt;zippery&lt;/em&gt;. (Mierzi mnie trochę używanie angielskiej nazwy, ale nie potrafię wymyślić dobrego polskiego odpowiednika tego słowa; będę szczęśliwy widząc propozycje w komentarzach!) Cóż to takiego?&lt;/p&gt;&lt;p&gt;&lt;a href="http://en.wikipedia.org/wiki/Zipper_(data_structure)"&gt;Zipper&lt;/a&gt; jest strukturą danych, dającą &lt;em&gt;iluzję imperatywności&lt;/em&gt; przy manipulowaniu drzewami. Pamiętamy, że Clojurowe natywne struktury danych są niezmienne (&lt;em&gt;immutable&lt;/em&gt;): jeśli mamy listę składającą się z liczb 2 i 5, to nie możemy jej w żaden sposób zmienić (np. dodać elementu) -- możemy co najwyżej stworzyć na jej podstawie &lt;em&gt;nową&lt;/em&gt; listę, która będzie zawierać wszystkie elementy listy wyjściowej i jeszcze jakiś.&lt;/p&gt;&lt;p&gt;Niezmienność jest bardzo przydatna (o filozoficznych podstawach takiego podejścia, które zadecydowały o jego wykorzystaniu w Clojure, można poczytać w artykule &lt;a href="http://clojure.org/state"&gt;„On State and Identity”&lt;/a&gt;), ale wymusza myślenie w innych kategoriach. To jest to, co czasami nazywa się „myśleniem funkcyjnym”: zamiast zastanawiać się, w jaki sposób &lt;em&gt;zmienić&lt;/em&gt; wartość naszej zmiennej, aby doprowadzić ją do pożądanego stanu, pytamy o to, &lt;em&gt;jak z jednych wartości robić inne&lt;/em&gt;. Przy tym nigdzie nie jest powiedziane, że taki sposób myślenia jest koniecznie lepszy od imperatywnego, do którego przyzwyczajeni są programiści języków takich jak Java. Jest po prostu inny. Warto się go nauczyć, bo okazuje się, że wiele problemów się upraszcza, gdy już umysł się przestawi na taki &lt;em&gt;modus operandi&lt;/em&gt;. Bywa jednak i tak, że o danym problemie imperatywnie myśli się wygodniej niż funkcyjnie. Tu właśnie wkraczają zippery.&lt;/p&gt;&lt;img src="/img/blog/zipper.png"&gt;
&lt;p&gt;Wyobraźmy sobie, że chcemy dodać liczbę 4 do konkretnego drzewa BST, tak jak na rysunku powyżej. Załóżmy chwilowo, że nie interesuje nas ogólna funkcja dodająca do BST, a tylko chcemy wstawić czwórkę w konkretne miejsce. Jak to zrobić imperatywnie?&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;span&gt;Zejdź dwa razy w dół w prawo („dobierz się” do węzła 6).&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;Wstaw czwórkę na lewo od bieżącego węzła.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;I już. A teraz funkcyjnie – pamiętamy, że z drzewa robimy nowe drzewo:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;span&gt;Nowym drzewem jest drzewo, którego korzeniem jest korzeń drzewa wyjściowego, lewym poddrzewem – lewe poddrzewo drzewa wyjściowego, a prawym poddrzewem drzewo, którego korzeniem jest korzeń prawego poddrzewa drzewa wyjściowego, lewym poddrzewem – drzewo składające się z tylko jednego węzła 4, a prawym poddrzewem – prawe poddrzewo prawego poddrzewa drzewa wyjściowego.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Pierwsze podejście jest łatwiejsze, prawda? Zipper pozwala nam zachować je prawie niezmienione, nie rezygnując przy tym z zalet niezmienności. Oto jak można by opisać dodawanie do drzewa z zipperem:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;span&gt;Stwórz zipper na podstawie drzewa wyjściowego.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;Wykonaj na tym zipperze operację „zejdź w dół”, otrzymując nowy zipper.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;Wykonaj na tym zipperze operację „przejdź w prawo”, otrzymując nowy zipper.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;Wykonaj na tym zipperze operację „zejdź w dół”, otrzymując nowy zipper.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;Wykonaj na tym zipperze operację „wstaw 4 przed bieżącym elementem”, otrzymując nowy zipper.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt;Wykonaj na tym zipperze operację „daj drzewo wynikowe”, otrzymując pożądane drzewo.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Clojure zawiera implementację zipperów w bibliotece standardowej (w przestrzeni nazw &lt;code&gt;clojure.zip&lt;/code&gt;). Oto jak można by zapisać powyższy przykład w Clojure:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;def&lt;/span&gt; &lt;span class="hljs-title"&gt;nowe-drzewo&lt;/span&gt;
  (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;let&lt;/span&gt;&lt;/span&gt; [z1 (&lt;span class="hljs-name"&gt;zip&lt;/span&gt; drzewo)
        z2 (&lt;span class="hljs-name"&gt;down&lt;/span&gt; z1)
        z3 (&lt;span class="hljs-name"&gt;right&lt;/span&gt; z2)
        z4 (&lt;span class="hljs-name"&gt;down&lt;/span&gt; z3)
        z5 (&lt;span class="hljs-name"&gt;insert-left&lt;/span&gt; z4 &lt;span class="hljs-number"&gt;4&lt;/span&gt;)]
    (&lt;span class="hljs-name"&gt;root&lt;/span&gt; z5)))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(tu &lt;em&gt;explicite&lt;/em&gt; nazywam kolejne kroki obliczeń). Albo tak, używając makra &lt;code&gt;-&gt;&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;def&lt;/span&gt; &lt;span class="hljs-title"&gt;nowe-drzewo&lt;/span&gt;
  (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt; drzewo
      zip
      down
      right
      down
      (&lt;span class="hljs-name"&gt;insert-left&lt;/span&gt; &lt;span class="hljs-number"&gt;4&lt;/span&gt;)
      root)))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Proste i wygodne. Pod spodem zipper to po prostu oryginalne drzewo plus informacja o tym, w którym miejscu drzewa w tej chwili jesteśmy, plus lista „zmian”, jakie do tej pory zostały na nim wykonane.&lt;/p&gt;&lt;p&gt;Rozwiązując mój problem (na razie nie zdradzam, jaki), natknąłem się na potrzebę posiadania funkcji, która działałaby jak &lt;code&gt;map&lt;/code&gt;, ale na drzewach, a nie listach. To znaczy, przekształcałaby każdy element, niezależnie od tego, jak głęboko w drzewie siedzi, aplikując do niego jakąś funkcję, i zwracała w wyniku nowe drzewo. Co więcej, chciałem móc dodatkowo kontrolować, które węzły są zmieniane: funkcja ma dostawać dodatkowy predykat i tylko kiedy zwróci on &lt;code&gt;true&lt;/code&gt; na wartości danego węzła, zmieniać tę wartość. Nie ma chyba takiej funkcji w standardowej bibliotece, ale dzięki zipperom można ją łatwo napisać.&lt;/p&gt;&lt;p&gt;Tak się szczęśliwie składa, że Clojurowe zippery mają funkcję &lt;code&gt;next&lt;/code&gt;, która spaceruje po drzewie „w głąb”. Mając je, mogłem pomyśleć tak o swojej implementacji:&lt;/p&gt;&lt;p&gt;Jeśli zipper jest na końcu drzewa, to wynikiem transformacji jest wynikowe drzewo dla tego zippera, w przeciwnym razie wynikiem jest ta sama transformacja wywołana rekurencyjnie dla zippera uzyskanego przez wykonanie operacji „edit” (edycji bieżącego węzła w razie potrzeby) i „next”.&lt;/p&gt;&lt;p&gt;Tłumacząc to na Clojure, dostajemy:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;defn&lt;/span&gt; &lt;span class="hljs-title"&gt;map-zipper&lt;/span&gt; [f pred z]
  (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;if&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;zip/end?&lt;/span&gt; z)
    (&lt;span class="hljs-name"&gt;zip/root&lt;/span&gt; z)
    (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;recur&lt;/span&gt;&lt;/span&gt; f pred (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt; z (&lt;span class="hljs-name"&gt;zip/edit&lt;/span&gt; #(&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;if&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;pred&lt;/span&gt; %) (&lt;span class="hljs-name"&gt;f&lt;/span&gt; %) %)) zip/next)))))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I testujemy:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;&amp;gt; (&lt;span class="hljs-name"&gt;map-zipper&lt;/span&gt; inc integer?
              (&lt;span class="hljs-name"&gt;zip/vector-zip&lt;/span&gt; [&lt;span class="hljs-number"&gt;1&lt;/span&gt; [&lt;span class="hljs-number"&gt;2&lt;/span&gt; [&lt;span class="hljs-number"&gt;3&lt;/span&gt; &lt;span class="hljs-number"&gt;4&lt;/span&gt;]] [&lt;span class="hljs-number"&gt;5&lt;/span&gt;] [&lt;span class="hljs-number"&gt;6&lt;/span&gt; [[&lt;span class="hljs-number"&gt;7&lt;/span&gt; &lt;span class="hljs-number"&gt;8&lt;/span&gt; &lt;span class="hljs-number"&gt;9&lt;/span&gt;]]]]))
[&lt;span class="hljs-number"&gt;2&lt;/span&gt; [&lt;span class="hljs-number"&gt;3&lt;/span&gt; [&lt;span class="hljs-number"&gt;4&lt;/span&gt; &lt;span class="hljs-number"&gt;5&lt;/span&gt;]] [&lt;span class="hljs-number"&gt;6&lt;/span&gt;] [&lt;span class="hljs-number"&gt;7&lt;/span&gt; [[&lt;span class="hljs-number"&gt;8&lt;/span&gt; &lt;span class="hljs-number"&gt;9&lt;/span&gt; &lt;span class="hljs-number"&gt;10&lt;/span&gt;]]]]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Trzeba podać predykat &lt;code&gt;integer?&lt;/code&gt;, bo zipper działający na zagnieżdżonych wektorach działa na poszczególnych poddrzewach, a nie tylko na liściach (które tu są liczbami). Możemy łatwo zobaczyć, jakie poddrzewa odwiedza &lt;code&gt;map-zipper&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;&amp;gt; (&lt;span class="hljs-name"&gt;map-zipper&lt;/span&gt; #(&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;do&lt;/span&gt;&lt;/span&gt; (&lt;span class="hljs-name"&gt;println&lt;/span&gt; %) %)
              (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;constantly&lt;/span&gt;&lt;/span&gt; &lt;span class="hljs-literal"&gt;true&lt;/span&gt;)
              (&lt;span class="hljs-name"&gt;zip/vector-zip&lt;/span&gt; [&lt;span class="hljs-number"&gt;1&lt;/span&gt; [&lt;span class="hljs-number"&gt;2&lt;/span&gt; [&lt;span class="hljs-number"&gt;3&lt;/span&gt; &lt;span class="hljs-number"&gt;4&lt;/span&gt;]] [&lt;span class="hljs-number"&gt;5&lt;/span&gt;] [&lt;span class="hljs-number"&gt;6&lt;/span&gt; [[&lt;span class="hljs-number"&gt;7&lt;/span&gt; &lt;span class="hljs-number"&gt;8&lt;/span&gt; &lt;span class="hljs-number"&gt;9&lt;/span&gt;]]]]))
[&lt;span class="hljs-number"&gt;1&lt;/span&gt; [&lt;span class="hljs-number"&gt;2&lt;/span&gt; [&lt;span class="hljs-number"&gt;3&lt;/span&gt; &lt;span class="hljs-number"&gt;4&lt;/span&gt;]] [&lt;span class="hljs-number"&gt;5&lt;/span&gt;] [&lt;span class="hljs-number"&gt;6&lt;/span&gt; [[&lt;span class="hljs-number"&gt;7&lt;/span&gt; &lt;span class="hljs-number"&gt;8&lt;/span&gt; &lt;span class="hljs-number"&gt;9&lt;/span&gt;]]]]
&lt;span class="hljs-number"&gt;1&lt;/span&gt;
[&lt;span class="hljs-number"&gt;2&lt;/span&gt; [&lt;span class="hljs-number"&gt;3&lt;/span&gt; &lt;span class="hljs-number"&gt;4&lt;/span&gt;]]
&lt;span class="hljs-number"&gt;2&lt;/span&gt;
[&lt;span class="hljs-number"&gt;3&lt;/span&gt; &lt;span class="hljs-number"&gt;4&lt;/span&gt;]
&lt;span class="hljs-number"&gt;3&lt;/span&gt;
&lt;span class="hljs-number"&gt;4&lt;/span&gt;
[&lt;span class="hljs-number"&gt;5&lt;/span&gt;]
&lt;span class="hljs-number"&gt;5&lt;/span&gt;
[&lt;span class="hljs-number"&gt;6&lt;/span&gt; [[&lt;span class="hljs-number"&gt;7&lt;/span&gt; &lt;span class="hljs-number"&gt;8&lt;/span&gt; &lt;span class="hljs-number"&gt;9&lt;/span&gt;]]]
&lt;span class="hljs-number"&gt;6&lt;/span&gt;
[[&lt;span class="hljs-number"&gt;7&lt;/span&gt; &lt;span class="hljs-number"&gt;8&lt;/span&gt; &lt;span class="hljs-number"&gt;9&lt;/span&gt;]]
[&lt;span class="hljs-number"&gt;7&lt;/span&gt; &lt;span class="hljs-number"&gt;8&lt;/span&gt; &lt;span class="hljs-number"&gt;9&lt;/span&gt;]
&lt;span class="hljs-number"&gt;7&lt;/span&gt;
&lt;span class="hljs-number"&gt;8&lt;/span&gt;
&lt;span class="hljs-number"&gt;9&lt;/span&gt;
[&lt;span class="hljs-number"&gt;1&lt;/span&gt; [&lt;span class="hljs-number"&gt;2&lt;/span&gt; [&lt;span class="hljs-number"&gt;3&lt;/span&gt; &lt;span class="hljs-number"&gt;4&lt;/span&gt;]] [&lt;span class="hljs-number"&gt;5&lt;/span&gt;] [&lt;span class="hljs-number"&gt;6&lt;/span&gt; [[&lt;span class="hljs-number"&gt;7&lt;/span&gt; &lt;span class="hljs-number"&gt;8&lt;/span&gt; &lt;span class="hljs-number"&gt;9&lt;/span&gt;]]]]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content>
  </entry>
  <entry>
    <id>tag:plblog.danieljanus.pl,2010-11-19:post:filmujmy-bledy</id>
    <title>Filmujmy błędy!</title>
    <link href="http://plblog.danieljanus.pl/filmujmy-bledy/"/>
    <updated>2010-11-19T00:00:00Z</updated>
    <content type="html">&lt;div&gt;&lt;p&gt;Każdy programista (o ile jego oprogramowanie jest używane przez kogoś jeszcze poza nim samym) dostał kiedyś zgłoszenie błędu w programie. Wielu pracuje na co dzień z systemami śledzenia błędów, wielu zgłasza błędy w programach, których sami używają.&lt;/p&gt;&lt;p&gt;Dobre zgłoszenie powinno pomóc programiście odtworzyć błąd. Instrukcja odtworzenia jest bodaj najważniejszym z trzech nieodzownych elementów zgłoszenia. (Pozostałe dwa, jak pisze &lt;a href="http://www.joelonsoftware.com/articles/fog0000000029.html"&gt;Joel Spolsky&lt;/a&gt;, to odpowiedzi na pytania: &lt;em&gt;co powinno było się stać&lt;/em&gt; oraz &lt;em&gt;co naprawdę się stało&lt;/em&gt;.) Na ogół w systemach śledzenia błędów opisujemy po prostu prozą kroki, które wykonaliśmy, a które doprowadziły do błędnej sytuacji.&lt;/p&gt;&lt;p&gt;Dokładne opisanie wykonanych czynności jest jednak trudne. Znaczenie może mieć każdy, nawet najmniejszy szczegół, łatwo więc o nieścisłość. Zamiast „otworzyłem plik” lepiej napisać „nacisnąłem Alt-O” albo „kliknąłem w menu Plik i kliknąłem polecenie Otwórz”, w zależności od tego, co naprawdę zrobiliśmy. W świetnym eseju &lt;a href="http://www.chiark.greenend.org.uk/~sgtatham/bugs-pl.html"&gt;&lt;em&gt;Jak efektywnie zgłaszać błędy&lt;/em&gt;&lt;/a&gt;, z którego zaczerpnąłem ten przykład, Simon Tatham podaje inną, efektywniejszą metodę zgłaszania: należy posadzić programistę przed swoim komputerem i pokazać mu, w jaki sposób doprowadzamy do błędu.&lt;/p&gt;&lt;p&gt;Ta metoda jest dobra, kiedy da się zastosować. Problem pojawia się w przypadku, kiedy programista jest fizycznie na drugim końcu świata. Nie może wtedy zobaczyć monitora użytkownika i patrzeć mu na ręce... albo przynajmniej do niedawna nie mógł.&lt;/p&gt;&lt;p&gt;Od jakiegoś czasu w &lt;a href="http://fablo.pl"&gt;Fablo&lt;/a&gt; dołączamy do zgłoszeń błędów krótkie filmiki – &lt;em&gt;screencasty&lt;/em&gt; z komentarzem głosowym. Z każdym kolejnym błędem rozwiązanym dzięki wskazaniu w taki sposób coraz bardziej przekonuję się do rozlicznych zalet tej metody. Przede wszystkim film jest dokładnym zapisem sesji. Widać wszystko, co się działo krok po kroku, i nie trzeba prosić o dodatkowe szczegóły, co zabiera czas i naprawiającego, i zgłaszającego. Film można odtwarzać wielokrotnie i dowolnie po nim nawigować, a nawet spowalniać; to nawet lepsze niż patrzenie na żywo na pracę użytkownika z programem. Jeśli mamy do czynienia z &lt;em&gt;heisenbugiem&lt;/em&gt; („wywala się mniej więcej co pięć minut i w różnych sytuacjach, za cholerę nie mam pomysłu!”), możemy po prostu włączyć nagrywanie i zapomnieć o nim, pracując normalnie do momentu wystąpienia błędu. Film można wtedy obciąć do ostatnich dwóch-trzech minut i wysłać.&lt;/p&gt;&lt;p&gt;Co więcej, sporządzenie filmiku bardzo mało kosztuje. Na obie używane przez nas platformy dostępne są narzędzia pozwalające łatwo nagrać film. Dzięki &lt;a href="http://www.ambrosiasw.com/utilities/snapzprox/"&gt;Snapz Pro X&lt;/a&gt; na Mac OS X nagranie jest tak proste jak naciśnięcie Shift-Cmd-3 na początku i na końcu filmu; &lt;a href="http://recordmydesktop.sourceforge.net/about.php"&gt;Record My Desktop&lt;/a&gt; na Linuksa i FreeBSD jest nieco trudniejszy w obsłudze, ale wciąż łatwy.&lt;/p&gt;&lt;p&gt;Wypadałoby wspomnieć o minusach filmów. Przychodzą mi do głowy dwa. Jednym jest fakt, że nie każdy błąd można zgłosić w ten sposób, a mówiąc ściślej: błąd w nie każdym oprogramowaniu. Gdy pracuje się z bibliotekami zamiast z okienkowym lub przeglądarkowym oprogramowaniem, wyłapywane błędy są innej natury i polegają na ogół na nieoczekiwanym zachowaniu konkretnej funkcji lub klasy. To, &lt;em&gt;nolens volens&lt;/em&gt;, trzeba opisać.&lt;/p&gt;&lt;p&gt;Druga sprawa to ilość miejsca zajmowanego przez filmiki i czasu potrzebnego na ich wrzucenie do systemu kontroli błędów. To zapewne jeszcze do niedawna był powód, dla którego rzadko widzi się takie zgłoszenia np. w oprogramowaniu &lt;em&gt;open source&lt;/em&gt;. Jednak problem ten staje się mało znaczący wraz z upowszechnieniem się szybkich łącz. Typowa wielkość naszego filmiku to ok. 1.5-2 MB; to naprawdę nie jest dużo, a &lt;a href="http://fogbugz.com"&gt;FogBugz&lt;/a&gt; pozwala na bardzo łatwe – jednym przeciągnięciem myszy – dodanie screencastu do raportu błędu. Większe rzeczy z łatwością można udostępnić w &lt;a href="http://dropbox.com"&gt;Dropbox&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Filmujmy więc błędy!&lt;/p&gt;&lt;/div&gt;</content>
  </entry>
  <entry>
    <id>tag:plblog.danieljanus.pl,2010-11-11:post:leniwa-wersja-makra</id>
    <title>Leniwa wersja makra -&gt;</title>
    <link href="http://plblog.danieljanus.pl/leniwa-wersja-makra/"/>
    <updated>2010-11-11T00:00:00Z</updated>
    <content type="html">&lt;div&gt;&lt;p&gt;&lt;a href="http://jaceklaskowski.pl"&gt;Jacek Laskowski&lt;/a&gt; podaje ciekawy &lt;a href="http://blog.japila.pl/2010/11/monady-maybe-odsona-kolejna-rozwiazanie.html"&gt;przykład wykorzystania monad w Clojure&lt;/a&gt; -- aplikowanie kolejnych funkcji do wyrażenia, dopóki ma ono wartość nie będącą &lt;code&gt;nil&lt;/code&gt;.  Przykład mi się podoba, bo jest prosty, ale nie trywialny – wykorzystuje monadę &lt;code&gt;maybe&lt;/code&gt; do eleganckiego rozwiązania rzeczywistego problemu.  Jest to w dodatku problem, z którym borykają się czasem programiści piszący w Javie, co widać choćby w &lt;a href="http://groups.google.com/group/pl.comp.lang.java/browse_thread/thread/d290ced113686b34/f4cc877a5053969d"&gt;tym wątku&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Nie mogę powiedzieć, że rozumiem monady. Owszem, znam definicję i potrafię napisać program w Haskellu wykorzystujący monadę &lt;code&gt;IO&lt;/code&gt;, ale każdy monadyczny kod, jaki dotychczas widziałem, dawał się zapisać równie zwięźle bez użycia monad w językach, które mniej religijnie podchodzą do niewpływania funkcji na stan świata zewnętrznego. Tak jest również w tym przypadku.&lt;/p&gt;&lt;p&gt;Jacek tak postawił oryginalny problem:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Napisać metodę, która zwraca walutę, dla pracownika z danego departamentu międzynarodowej korporacji. Pracownik jest przypisany do departamentu (np. poprzez mapę – pracownik-departament), departament do kraju, a kraj do waluty. Funkcja na wejściu dostaje nazwę, identyfikator, lub cokolwiek jednoznacznie reprezentującego pracownika, a na wyjściu symbol waluty, np. dla „Jacek” powinno być „PLN”, a dla „John” „USD”, a „Tomek” i „Mateusz” dawaliby „CHF”.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Moje pierwotne rozwiązanie jest po prostu złożeniem trzech funkcji:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;defn&lt;/span&gt; &lt;span class="hljs-title"&gt;pracownik-&amp;gt;waluta&lt;/span&gt; [p]
  (&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;-&amp;gt;&lt;/span&gt;&lt;/span&gt; p pracownik-&amp;gt;departament departament-&amp;gt;kraj kraj-&amp;gt;waluta))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;O ile wszystkie z nich są mapami (pamiętamy, że w Clojure mapy implementują interfejs &lt;code&gt;IFn&lt;/code&gt;, można je więc uważać za funkcje i wołać jak funkcje), to dla żadnej wartości wejściowej nie zostanie rzucony &lt;code&gt;NullPointerException&lt;/code&gt;, ponieważ wywołanie mapy dla nieistniejącego klucza zwraca &lt;code&gt;nil&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Jacek słusznie zauważa jednak, że wykonujemy w ten sposób więcej pracy niż trzeba. Jeżeli już w mapie &lt;code&gt;pracownik-&gt;departament&lt;/code&gt; nie ma departamentu dla danego pracownika, to otrzymany &lt;code&gt;nil&lt;/code&gt; zostanie „przepchnięty” przez pozostałe dwie mapy, zanim będzie zwrócony. Można kontrargumentować, że taka implementacja &lt;code&gt;pracownik-&gt;waluta&lt;/code&gt; jest bardzo czytelna i mała strata wydajności jest niewielką ceną do zapłacenia za tę czytelność.  Co jednak, gdy nie możemy lub nie chcemy zgodzić się na taką stratę, a jednocześnie nie chcemy stracić czytelności?&lt;/p&gt;&lt;p&gt;Odpowiedzią jest makro, które nazwałem &lt;code&gt;and-&gt;&lt;/code&gt;. Działa ono tak samo, jak &lt;code&gt;-&gt;&lt;/code&gt;, z tą różnicą, że kolejne wartości oblicza tylko jeśli po drodze nie pojawiło się &lt;code&gt;false&lt;/code&gt; lub &lt;code&gt;nil&lt;/code&gt;, podobnie jak &lt;code&gt;and&lt;/code&gt; (stąd nazwa). Oto ono:&lt;/p&gt;&lt;pre&gt;&lt;code class="hljs clojure"&gt;(&lt;span class="hljs-keyword"&gt;defmacro&lt;/span&gt; &lt;span class="hljs-title"&gt;and-&amp;gt;&lt;/span&gt;
  ([x] x)
  ([x form] `(&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;when-let&lt;/span&gt;&lt;/span&gt; [y# ~x] (~form y#)))
  ([x form &amp;amp; more] `(&lt;span class="hljs-name"&gt;&lt;span class="hljs-built_in"&gt;when-let&lt;/span&gt;&lt;/span&gt; [y# (&lt;span class="hljs-name"&gt;and-&amp;gt;&lt;/span&gt; ~x ~form)]
                      (&lt;span class="hljs-name"&gt;and-&amp;gt;&lt;/span&gt; y# ~@more))))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Implementacja jest bardzo zbliżona do zwykłego &lt;code&gt;-&gt;&lt;/code&gt;, którego kod w Clojure 1.2 wygląda &lt;a href="https://github.com/clojure/clojure/blob/1.2.0/src/clj/clojure/core.clj#L1284"&gt;tak&lt;/a&gt;. W stosunku do &lt;code&gt;-&gt;&lt;/code&gt; moje makro jest dla większej poglądowości trochę uproszczone i nie obsługuje konstrukcji typu &lt;code&gt;(and-&gt; mapa (get :klucz))&lt;/code&gt;, które normalnie rozwijane są do &lt;code&gt;(get mapa :klucz)&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Warto zwrócić uwagę na to, że argument makra nigdy nie jest wyliczany w jego rozwinięciu więcej niż raz; zawsze jest wiązany do lokalnego symbolu o unikatowej nazwie za pomocą &lt;code&gt;when-let&lt;/code&gt;. Jest to jedna z reguł pozwalających unikać błędów w makrach. Szerszy opis tego problemu, występującego też w Common Lispie, można znaleźć w &lt;a href="http://gigamonkeys.com/book/macros-defining-your-own.html#plugging-the-leaks"&gt;rozdziale 8 książki „Practical Common Lisp”&lt;/a&gt;.&lt;/p&gt;&lt;h2 id="aktualizacja-(7.12.2010)"&gt;Aktualizacja (7.12.2010)&lt;/h2&gt;&lt;p&gt;Jak się okazuje, clojure-contrib &lt;a href="http://clojuredocs.org/clojure_contrib/clojure.contrib.core/-_q%3E"&gt;zawiera już&lt;/a&gt; takie makro! Nazywa się &lt;code&gt;-?&gt;&lt;/code&gt; i można je znaleźć w przestrzeni nazw &lt;code&gt;clojure.contrib.core&lt;/code&gt;. To już któryś raz, kiedy okazuje się, że jakieś użyteczne makro lub funkcja już jest w contrib i nie trzeba jej było pisać.&lt;/p&gt;&lt;/div&gt;</content>
  </entry>
</feed>
