lambdaniel http://plblog.danieljanus.pl Daniel Janus o języku, typografii, książkach, programowaniu w Clojure i nie tylko posterous.com Tue, 10 Jan 2012 04:42:41 -0800 Praktyczne użycie monady state http://plblog.danieljanus.pl/praktyczne-uzycie-monady-state http://plblog.danieljanus.pl/praktyczne-uzycie-monady-state

Wreszcie rozumiem monady!

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 naprawdę 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 tutorial. Ja się powstrzymam od tego naturalnego odruchu i po prostu odeślę do niesamowicie szczegółowego, ośmioczęściowego cyklu artykułów 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 state.

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.

Oto więc moje notatki:

  • Kluczowe dla zrozumienia monad jest pojęcie funkcji monadycznych; znacznie łatwiej jest je sobie intuicyjnie wyobrazić niż monadyczne wartości, zwracane przez m-return. 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.
  • Łatwo wydefiniować przy użyciu prostego złożenia operacji m-bind i m-return funkcję składającą ze sobą dwie funkcje monadyczne. Przy użyciu tego operatora, który Mike nazywa <=<, prawa rządzące monadami wyrażają się w elegancki i prosty do zapamiętania sposób: jest to operator łączny oraz m-return jest jego lewą i prawą jedynką.
  • 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 do-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.

Reszta tego wpisu będzie poświęcona właśnie przykładowemu użyciu monady stanu w Clojure. Pisałem wcześniej, ż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.

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.

(def v [1 1 1 0 1 0 1 0 1 0])

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.

(defn read-binary [n bits]
  (loop [res 0 n n [fst & rst :as bs] bits]
    (if (zero? n)
      [res bs]
      (recur (+ res res fst) (dec n) rst))))

Spróbujmy wczytać z naszego strumienia czterobitową liczbę:

(read-binary 4 v)
;=> [14 (1 0 1 0 1 0)]

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 reszta strumienia, z ktróej będziemy czytać dalej. Piszemy wszak funkcyjnie: nie zmieniamy naszych wartości w miejscu, tylko z jednych wartości produkujemy następne.

Innym rodzajem kodu jest kod unarny: 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ą n da się zareprezentować na n bitach. Implementacja jest równie prosta:

(defn read-unary [bits]
  (loop [n 1 [fst & rst] bits]
    (if (zero? fst)
      [n rst]
      (recur (inc n) rst))))

Sprawdzamy:

(read-unary v)
;=> [4 (1 0 1 0 1 0)]

Trzy jedynki i zero, razem cztery skonsumowane bity, więc odczytaną liczbą jest 4. Działa.

Spróbujmy teraz skleić nasze funkcje, to znaczy odczytać ze strumienia dwie liczby: najpierw zakodowaną unarnie, a potem binarnie na sześciu bitach.

(let [[x v1] (read-unary v)
      [y v2] (read-binary 6 v1)]
  [x y])
;=> [4 42]

Wynik jest poprawny, ale kod nieelegancki: tak naprawdę nie interesują nas te wszystkie v, v1, v2, 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ć.

Tu wkracza monada stanu, która umożliwia ukrycie tych pośrednich wartości i bardziej eleganckie złożenie read-unary i read-binary. Monadycznie zapisalibyśmy to jakoś tak:

(domonad state-m
         [x read-unary
          y (partial read-binary 6)]
         [x y])
;=> #

Wygląda to bardzo podobnie. domonad state-m jest jak let. Tak jak chcieliśmy, nie przekazujemy naszego stanu (strumienia) explicite; zamiast tego przy x i y podajemy funkcje, które mają być zawołane na aktualnym stanie, żeby uzyskać żądaną wartość i nowy stan.

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:

(*1 v)
;=> [[4 42] nil]

Jedyne, co wydaje się nadmiarowe w naszym złożeniu, to owo partial 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 domonad state-m 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ą partial. Alternatywnie, jeżeli chcemy trzymać się stylu monadycznego, możemy przepisać read-binary jako:

(defn read-binary [n]
  (fn [bits]
    (loop [res 0 n n [fst & rst :as bs] bits]
      (if (zero? n)
        [res bs]
        (recur (+ res res fst) (dec n) rst)))))

Teraz zamiast (partial read-binary 6) możemy napisać po prostu (read-binary 6).

Teraz, kiedy umiemy już składać operacje stanowe, zilustrujemy to przy pomocy jeszcze jednego kodu, a właściwie rodziny kodów nazywanych kodami Golomba. Po szczegółowy opis odsyłam do Wikipedii, natomiast dla potrzeb tego artykułu wystarczy nam wiedza, że te kody są parametryzowane jedną liczbą m. Liczba w kodzie Golomba jest podzielona na dwie części, z których pierwsza jest zakodowana unarnie, a druga — nie większa niż m — binarnie. W implementacji możemy więc skorzystać z naszych gotowych już funkcji read-unary i read-binary.

Bez dłuższych już komentarzy przedstawię dwie implementacje: eksplicytną i używającą monady stanu. Obie będą używać pomocniczej funkcji read-bit, czytającej po prostu jeden bit ze strumienia i używanej tak samo jak read-unary czy read-binary.

(defn read-bit [[bit & bits]]
  [bit bits])

Najpierw wersja niemonadyczna:

(defn read-golomb [m]
  (fn [bits]
    (let [k (clog2 m)
          r (- (bit-shift-left 1 k) m)
          [a bits1] (read-unary bits)
          [b bits2] ((read-binary (dec k)) bits1)
          [ir bits] (if (< b r)
                      [b bits]
                      (let [[nb bits] (read-bit bits)]
                        [(+ b b nb (- r)) bits]))]
      [(+ (* (dec a) m) ir) bits])))

A teraz używająca monad. Żeby nie przeplatać let i domonad, można po prostu zdefiniować funkcję zwracającą pewną stałą wartość i niezmieniony stan — nazwę ją m-const:

(defn m-const [x]
  (fn [state]
    [x state]))

Używając jej, możemy zaimplementować read-golomb tak:

(defn read-golomb-monadic [m]
  (domonad state-m
           [k (m-const (clog2 m))
            r (m-const (- (bit-shift-left 1 k) m))
            a read-unary
            b (read-binary (dec k))
            nb (if (< b r) (m-const nil) read-bit)]
           (+ (* (dec a) m)
              (if (< b r) b (+ b b nb (- r))))))

Czytelniej?

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1334473/nat-face-square.jpeg http://posterous.com/users/YXpC5IC5SCt Daniel Janus danieljanus Daniel Janus
Sun, 04 Sep 2011 16:01:00 -0700 Spóźniony post na Blog Day 2011 http://plblog.danieljanus.pl/spozniony-post-na-blog-day-2011 http://plblog.danieljanus.pl/spozniony-post-na-blog-day-2011

Przegapiłem tegoroczny Dzień Blogów. Ale kto powiedział, że notka zawierająca odnośniki do pięciu lubianych i czytanych przeze mnie blogów nie ma prawa pojawić się kilka dni po kanonicznej dacie? Oto więc one:

  • Notatnik zapisywany wieczorami: na tej liście teoretycznie nie powinno być blogów z “mojej branży” czyli okołoinformatycznych, ale nie mogę tu nie umieścić odnośnika do zapisków Marcina Kasperskiego. Bo też nie są one stricte techniczne (z ostatnich miesięcy przychodzi mi na myśl świetna notka o autorytetach), a są dla mnie wzorem: setki wpisów, a każdy obszerny, wartościowy i zawierający oryginalne przemyślenia lub cenną syntezę wiedzy. Marcin nie stara się zaskarbić sobie czytelników akcjami marketingowymi i nie dba specjalnie o rozgłos. Po prostu robi swoje: pisze ciekawie. Bardzo, bardzo bym chciał, żeby takich blogów było więcej.
  • Migotanie słów: Polski matematyk w Brazylii: o kulturze, o historii, o brazylijskich doświadczeniach i, jakże by inaczej, o matematyce. Jeśli miałbym określić ten blog jednym przymiotnikiem, byłoby to słowo “mądry”. Dziękuję, Andrzeju.
  • Widziane z okna na strychu: Można by powiedzieć, że to blog feministyczny, ale to bardzo spłycające określenie. Jak dotąd, siedem wpisów. Ale jakich! Już pierwszy ujął mnie stylem i roztropnością – chciałbym umieć pisać takim językiem i z takim wyczuciem o trudnych sprawach.
  • Haderech, czyli droga: O ilu miejscach w sieci mogę powiedzieć “pamiętam, jak tu trafiłem”? O tym tak, choć od roku nie jest już aktualizowane. Szukałem, skąd się wziął tekst “Who By Fire” Cohena — jak się okazuje, jest on zaczerpnięty z Unetane Tokef, dwunastowiecznej hebrajskiej modlitwy. Bardzo ciekawe studium odkrywania własnej tożsamości i odkrywania starodawnej ścieżki. Polecam i podziwiam.
  • Złote myśli Piotra S.: zanim Twitter stał się, ten blog już był. Trudno mi oddać jego specyfikę, polecam po prostu zajrzenie i doświadczenie specyficznego humoru autora :–)

I na zakończenie ciekawostka liczbowa: trzy z opisywanych blogów prowadzone są w serwisie Blox, a dwa używają autorskich silników. Go figure.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1334473/nat-face-square.jpeg http://posterous.com/users/YXpC5IC5SCt Daniel Janus danieljanus Daniel Janus
Sun, 24 Jul 2011 04:17:00 -0700 MazUNIX Dąbrowskiego http://plblog.danieljanus.pl/mazunix-dabrowskiego http://plblog.danieljanus.pl/mazunix-dabrowskiego
Jeszcze Polska nie zginęła,    $ (ps ax | grep Polska) 
póki my żyjemy.                  && (ps ax | grep `whoami`)
Co nam obca przemoc wzięła,    $ sudo chown -R / `whoami`
szablą odbierzemy.
  Marsz, marsz, Dąbrowski,     $ ./Dabrowski 
  z ziemi włoskiej do Polski.    </dev/ziemia-wloska >/dev/polska
  Za twoim przewodem           $ ifconfig eth0 up
  złączym się z narodem.       $ telnet polska.pl

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1334473/nat-face-square.jpeg http://posterous.com/users/YXpC5IC5SCt Daniel Janus danieljanus Daniel Janus
Thu, 21 Jul 2011 09:57:00 -0700 ClojureScript http://plblog.danieljanus.pl/clojurescript http://plblog.danieljanus.pl/clojurescript

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ę ogłoszenie, ż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”.

Tym czymś okazał się ClojureScript: kompilator Clojure do JavaScriptu, łączący moc pierwszego języka z powszechnością drugiego.

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.

A teraz te same słowa można odnieść również do Clojure. W tegorocznym sondażu, 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!

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ść.

Jaka przyszłość czeka Clojure i ClojureScript? Spróbuję pokusić się o przepowiednię:

  • 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ę (.toUpperCase "foo") (wychodząc z założenia where Java isn’t broken, Clojure doesn’t fix it), teraz kanoniczną formą jest (clojure.string/upper-case "foo"). W ten sposób Clojure stopniowo stanie się językiem niezabawkowym według definicji Maćka Pasternackiego.
  • 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.
  • 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 final jest kwestią kilku tygodni.

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.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1334473/nat-face-square.jpeg http://posterous.com/users/YXpC5IC5SCt Daniel Janus danieljanus Daniel Janus
Sun, 17 Jul 2011 04:05:55 -0700 Jeszcze o matematyce w szkole http://plblog.danieljanus.pl/jeszcze-o-matematyce-w-szkole http://plblog.danieljanus.pl/jeszcze-o-matematyce-w-szkole

Zaczęło się od listu maturzystki opublikowanego przez Gazetę Wyborczą, zawierającego narzekanie na temat obowiązkowości matury z matematyki. Rozpętała się burza: przez Internet przetoczyła się fala krytyki, w większości odsądzającej autorkę od czci i wiary. Moja własna reakcja była nieco bardziej stonowana; parę dni temu odkryłem, że została również opublikowana przez Wyborczą razem z innymi opiniami.

A dziś, szukając zupełnie czego innego, natknąłem się przypadkiem na ten artykuł Paula Lockharta. I zgadzam się z absolutnie każdym słowem; w swoim liście próbowałem powiedzieć mniej więcej to samo, ale nie potrafiłem ująć tego ani w jednej dziesiątej tak dobrze.

Przeczytajcie ten artykuł. Teraz. Uważam, że jest lekturą obowiązkową dla obu stron barykady: i antytalentów matematycznych, i ludzi twardo broniących matury z matematyki w jej obecnym kształcie.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1334473/nat-face-square.jpeg http://posterous.com/users/YXpC5IC5SCt Daniel Janus danieljanus Daniel Janus
Sat, 07 May 2011 10:48:53 -0700 Informatyka jest gałęzią matematyki http://plblog.danieljanus.pl/informatyka-jest-galezia-matematyki http://plblog.danieljanus.pl/informatyka-jest-galezia-matematyki

Claygirl pisze na blogu GeekGirls, że wbrew powszechnemu mniemaniu, informatyka to nie matematyka — i że informatykowi znajomość matematyki jest potrzebna w bardzo ograniczonym zakresie. Zdziwiłem się, bo jestem odmiennego zdania i zawsze wydawało mi się, że to właśnie pogląd, że informatyka jest częścią matematyki, jest w mniejszości… Zapewne jednak większość klepaczy kodu rzadko zadaje sobie to pytanie. Szkoda, bo warto stawiać fundamentalne pytania o to, czym w istocie jest to, czym się zajmujemy.

Temat jest mi bliski, postaram się więc w tym wpisie uzasadnić swoją opinię. Żeby móc sensownie odpowiedzieć na nasze pytanie, powinniśmy wpierw zdefiniować, co rozumiemy przez informatykę, a co przez matematykę. Zaczniemy więc od definicji encyklopedycznych.

Wikipedia dixit

Informatyka — dyscyplina naukowa i techniczna zajmująca się przetwarzaniem informacji, a w tym technologiami przetwarzania informacji oraz technologiami wytwarzania systemów przetwarzających informacje.

Myślę, że większość z nas zgodziłaby się z takim opisem. Aplikacje webowe, bazy danych, systemy operacyjne… czymkolwiek się zajmujemy, zawsze ma to związek z przetwarzaniem informacji. A matematyka?

(Zanim zajrzymy do Wikipedii, zachęcam do pokuszenia się o własną definicję. Może to przyprawić o ból głowy, bo jak objąć kilkoma słowami całe to bogactwo abstrakcyjnych bytów, jakimi matematyka się zajmuje? Liczby, zbiory, klasy, relacje, funkcje, punkty, przestrzenie, formuły, kategorie…)

Matematyka — nauka dostarczająca narzędzi do otrzymywania ścisłych wniosków z przyjętych założeń, zatem dotycząca prawidłowości rozumowania.

Ścisłość rozumowania i abstrakcje

Ta definicja w ogóle prześlizguje się nad owym bogactwem; jest szersza, bardziej pierwotna i może budzić sprzeciw. Zdaje się sugerować, że ilekroć myślimy o czymś — czymkolwiek — w sposób uporządkowany, zajmujemy się matematyką! A jednak wielu matematyków uważa, że to właśnie w myśleniu tkwi sedno ich dziedziny. Włodek Holsztyński ujmuje to w swoim świetnym eseju jeszcze zwięźlej:

Matematyka jest sztuką myślenia

Gdzie jednak przy takim podejściu postawić granicę pomiędzy matematyką a innymi naukami? Czy fizyka jest częścią matematyki, czy odrębną dziedziną wiedzy, która tylko korzysta z metod matematycznych? Wspominałem wcześniej o bytach abstrakcyjnych: wszyscy mamy mniej więcej zbliżone pojęcie na temat tego, czym jest liczba 7, mimo że taki obiekt nie istnieje fizycznie w otaczającym nas świecie. Wydaje się, że abstrakcyjność jest wpisana w naturę matematyki, i można ją dopisać do definicji. Wspomniany artykuł w Wikipedii zawiera inne próby zdefiniowania tej dziedziny przez sławnych matematyków, z których kilku o tym wspomina. Dirac: “Matematyka jest narzędziem stworzonym specjalnie do wszelkich abstrakcyjnych koncepcji, i nie ma ograniczeń dla jej potęgi w tym zakresie”.

A Poincaré mówi: “sztuka nadawania takich samych nazw różnym rzeczom”. Innymi słowy, zdolność dostrzegania podobieństw i abstrahowania od różnic. To jabłko jest czymś innym niż tamta gruszka, ale mają ze sobą coś wspólnego: liczność — tak zapewne brzmiało pierwsze, doniosłe odkrycie matematyczne.

Matematyka w szkole

…jest tylko drobnym wycinkiem wszystkich tych działów. To oczywiste, w końcu poświęca się jej ograniczoną ilość czasu. Ale za duży nacisk kładzie się na ich przedstawienie, a za mały na rozumienie. Nie jest ważne, żeby wiedzieć, ile jest siedem razy dziewięć. Ważne, żeby umieć w razie potrzeby się zastanowić i dojść do prawidłowej odpowiedzi. Rzadko pokazuje się dowody twierdzeń (i potem uczniowie panicznie boją się słowa “udowodnij”), a szkoda! Nie jest trudno zobaczyć, dlaczego twierdzenie Pitagorasa jest prawdziwe, albo dlaczego mnożenie liczb naturalnych jest przemienne. A tak właśnie ćwiczy się umiejętność myślenia.

A informatyka?

Clay pisze:

Ale całe dziesiątki poziomów abstrakcji wyżej siedzą sobie systemy operacyjne i maszyny wirtualne z którymi większość informatyków widuje się najczęściej. Informatyków – programistów języków wysokopoziomowych (takich jak Java, C#, Ruby, Python etc), którzy z matematyką muszą się znać tylko po to, żeby umieć myśleć logicznie i na przykład oszacować czas działania napisanego właśnie programu.

Już samo słowo “poziomy abstrakcji” kojarzy się z tym, o czym pisałem wcześniej. Oto mój program, mam w nim jakąś klasę.

public class Osoba {
    String imie, nazwisko;
    int wiek;
}

Ona modeluje jakiś obiekt rzeczywisty, ale sama w sobie jest równie abstrakcyjnym bytem jak liczba 7. Jest napisana w języku Java — mogę sięgnąć po formalny opis jego gramatyki i semantyki i ściśle opisać, czym jest. I wreszcie, pisząc taki kod, pomyślałem o tym, co poszczególne osoby mają wspólnego i abstrahowałem od nieistotnych dla mnie różnic między nimi.

Czy nie tym właśnie zajmuje się matematyka? W jaki sposób mój program albo język programowania jest mniej obiektem matematycznym, niż trójkąt albo zbiór {4, 42}?

Nie we wszystkim jesteśmy dobrzy

Pomijając terminologię, trzeba się zgodzić z tezą artykułu Clay: fakt, że nie czujemy się mocni w jednej gałęzi matematyki (nazwijmy ją “licealną”), wcale nie musi oznaczać, że nie rozwiniemy skrzydeł w innej. Nikt nie jest omnibusem, nierozumienie czegoś nie jest powodem do wstydu. Srinivasa Ramanujan, bodaj największy geniusz matematyczny ubiegłego stulecia, wniósł gigantyczny wkład do teorii liczb i wielu innych dziedzin… ale nie miał zielonego pojęcia, co to jest funkcja zmiennej zespolonej!

Tak i my nie musimy tego wiedzieć, pisząc kod czy grzebiąc w bebechach systemu. Miejmy jednak świadomość, że w istocie uprawiamy matematykę, która jest sztuką myślenia — i cieszmy się tym.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1334473/nat-face-square.jpeg http://posterous.com/users/YXpC5IC5SCt Daniel Janus danieljanus Daniel Janus
Mon, 18 Apr 2011 16:27:33 -0700 Wrażenia z java4people 2011 http://plblog.danieljanus.pl/wrazenia-z-java4people-2011 http://plblog.danieljanus.pl/wrazenia-z-java4people-2011

Jest niedzielny poranek, siedzę w pociągu relacji Szczecin-Warszawa i właśnie wyciągnąłem laptopa, aby spisać wrażenia z trzeciej edycji konferencji java4people, która odbyła się wczoraj w Szczecinie, póki jeszcze nie okrzepły i nie rozmyły się.

Zanim opowiem o poszczególnych wystąpieniach, winienem oddać zasłużone gratulacje organizatorom, czyli szczecińskiemu JUG-owi. Przestronne, nowoczesne aule Zachodniopomorskiego Uniwersytetu Technologicznego, dostępne przez cały czas ciastka i herbata, obiad (załapałem się na dokładkę!), after-party, a nade wszystko ciekawe prezentacje i rozmowy kuluarowe — wszystko to stwarzało znakomitą atmosferę sprzyjającą temu, aby posłuchać o nowych językach i technologiach wokół Javy i JVM oraz “dzielić się wiedzą i pasją”, jak głosi motto konferencji. Wielkie brawa!

Jedyny niedosyt polegał na tym, że prezentacje poprowadzone były w dwóch równoległych blokach, a do tego w trzecim bloku trwały warsztaty z SWT/JFace. Momentami bardzo doskwierał mi brak możliwości bilokacji, ale trudno winić o to organizatorów.

Oficjalnego otwarcia dokonał prof. Antoni Wiliński, dziekan Wydziału Informatyki ZUT, życząc uczestnikom owocnej wymiany doświadczeń. Następnie część ludzi udała się posłuchać wystąpienia Tomka Kopacza o .NET i Javie, ja zaś zadebiutowałem w roli prelegenta na imprezie tego formatu, opowiadając o Clojure.

Trudno mi wypowiadać się o własnej prezentacji — nie jestem dobrym mówcą i to było widać. Jak się jednak okazało z rozmów ze słuchaczami, udało się zainteresować część osób tematem. Sukces jest więc połowiczny, a postaram się, aby następnym razem było lepiej. Slajdy można znaleźć tu.

Potem wybrałem się na wystąpienie Przemka Pokrywki o Scali. Scala bywa wymieniana jednym tchem z Clojure, gdy mowa o nowych językach funkcyjnych działających na JVM, więc byłem bardzo ciekaw tego wykładu. I nie zawiodłem się: z Przemka wprost emanowała pasja, znać było też dużą wiedzę i doświadczenie w temacie. Przemek ciekawie pokazał, w jaki sposób Scala łączy paradygmat funkcyjny z obiektowym — w tym drugim zakresie przypomina mi Smalltalka: tu też wszystko jest obiektem i 2 + 3 oznacza zawołanie na obiekcie 2 metody + z parametrem 3. Fajnym mechanizmem wydaje się też możliwość definiowania własnych niejawnych konwersji — to sposób na wzbogacanie już istniejących klas o nowe funkcjonalności, ale bez zmiany tych klas; przypomina to trochę Clojurowe protokoły i extend-type. Dopisuję do listy rzeczy do wypróbowania.

Po obiedzie, zachęcony tytułem, wybrałem się na prezentację “Skalowalność technologii Javowych w zastosowaniach komercyjnych” Dawida Gruszczyńskiego. Organizatorzy konferencji na samym początku rozdali ankiety, w których prosili o ocenienie każdego wystąpienia w skali od 1 (szkoda czasu) do 5 (naprawdę warto) — po tej prezentacji dały się słyszeć rozmowy, że należałoby dać jej 0 albo i -1. Ja jestem łagodniejszy w swych ocenach. Bezsprzecznie prezentacja była źle zatytułowana: tytuł obiecywał opowieść o technikach czy narzędziach projektowania skalowalnych aplikacji; tymczasem okazało się to klasycznym success story dotyczącym systemu obsługi spisu powszechnego, w tym oprogramowania działającego na terminalach używanych przez rachmistrzów.

Czy ta prezentacja była nie na miejscu na takiej konferencji? Może i tak. Mam jednak słabość do success stories i duży szacunek do ludzi, którym udaje się stworzyć działające, niezawodne oprogramowanie, nawet jeśli nie zgadzam się z wyborem technologii. To naprawdę spora rzecz. A samej prelekcji trzeba oddać, że była sprawnie przeprowadzona, slajdy zrozumiałe, a diagramy prezentujące architekturę systemu — czytelne. Wątpliwości słuchaczy budziła kwestia bezpieczeństwa transmisji danych (i samych rachmistrzów!) Ciekawostka: terminale rachmistrzowskie mają zmienne IP, ale nie powiadamiają serwera co chwila o swoim aktualnym adresie, bo wywoływałoby to zanadto duże obciążenie. Co więc robi terminal, gdy zmieni mu się adres? Otóż… wysyła SMS (!) ze swoim aktualnym adresem!

Na wystąpieniu Sebastiana Pietrowskiego o Jythonie (a także samym Pythonie i Django) nie dowiedziałem się wiele nowego, z racji wcześniejszych swoich doświadczeń z Pythonem. Ale też celem tej prezentacji było chyba nie tyle przekazanie wiedzy, co podzielenie się własnymi doświadczeniami, z subiektywnego punktu widzenia — i to udało się Sebastianowi znakomicie. Stąd slajdy zatytułowane “What I Like”, “What I Don’t Like”, “What I’d Use Python For” — bardzo pragmatyczne. Sebastian opowiadał między innymi o tym, że Jythona można fajnie wykorzystać jako narzędzie do eksploracji nowych API z REPL-a: używając pythonowej introspekcji można łatwo obejrzeć listę pól i metod udostępnianych przez dowolną klasę. W swoim wystąpieniu o tym nie opowiadałem, ale w pakiecie repl-utils w Clojure Contrib jest funkcja show, która daje bardzo podobny efekt.

Słuchając wystąpienia Bartka Kuczyńskiego o Vaadinie utwierdziłem się w przekonaniu, że nie chcę pisać aplikacji webowych tak jak okienkowe, na modłę swingową. Vaadin ma w zamierzeniu służyć właśnie do tego: to nadbudowany nad GWT modularny framework WWW. Bartek pokazywał dziejącą się pod spodem magię, wraz z protokołem, którym część kliencka kompilowana do JS komunikuje się z serwerową (UIDL — dobrze pamiętam? — i JSON z danymi do wyświetlenia, poprzedzonymi ni mniej ni więcej tylko pętlą nieskończoną for(;;);, co podobno ma jakiś sens). Psikus w tym, że ostatnio w Smyrnie zrobiłem dokładnie odwrotną rzecz, to znaczy napisałem aplikację desktopową tak jakbym pisał webową, a więc używając ajaksowego XML-RPC (odsyłam do niedawnego postu na ten temat), i wyszło na oko prościej niż w Vaadinie. Może opowiem o tym na Warszawa JUG?

Nie zdążyłem się obejrzeć, a to już koniec konferencji! Do dotychczasowych bonusów dołączył kolejny: wśród wypełniających ankiety organizatorzy rozlosowali pięć książek. Los wytypował mnie jeszcze raz i schowałem do plecaka “Domain-Specific Languages” Martina Fowlera.

Pokonferencyjne after-parties bywają bodaj ważniejszymi nawet elementami takich spotkań niż same prezentacje. To tam toczy się najwięcej rozmów, to tam zawiązują się nowe znajomości i trwają nieskrępowane, długie dyskusje. Tak bywa z pizzą na Auli, tak było i teraz. Wielkie dzięki raz jeszcze organizatorom, wszystkim, których poznałem, z którymi udało mi się porozmawiać, i tym, którzy przebrnęli przez moje wystąpienie. Do zobaczenia przy innej okazji!

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1334473/nat-face-square.jpeg http://posterous.com/users/YXpC5IC5SCt Daniel Janus danieljanus Daniel Janus
Mon, 14 Mar 2011 09:31:42 -0700 Nadchodzi Wayland http://plblog.danieljanus.pl/nadchodzi-wayland http://plblog.danieljanus.pl/nadchodzi-wayland

Podczas jednego z ostatnich surfathlonów rzuciła mi się w oczy informacja o nowym kawałku uniksowego oprogramowania. Rozwijany bez wielkiego rozgłosu i dziś niemal nieznany, za kilka lat prawdopodobnie będzie jednym z kluczowych elementów absolutnie każdego biurkowego Linuksa. Mowa o serwerze wyświetlania Wayland, który ma wielką szansę stać się sensowną alternatywą dla X Window System.

X-y mają jedną kluczową zaletę. To wielki kawał oprogramowania bardzo intensywnie testowanego od dziesiątków lat, który działa. A jednak ilekroć uruchamiam ps ax lub top i widzę działający na moim komputerze, zżerający kilka procent czasu procesora i kilkanaście procent pamięci serwer X, mierzi mnie, gdy pomyślę, co tam się dzieje pod spodem.

X Window System narodził się jako protokół wyświetlania, pomyślany z wielkim rozmachem na początku lat 80. tak, aby w pełni wykorzystać moc ówczesnych stacji roboczych z wielkimi, monochromatycznymi wyświetlaczami. Bezstanowy model wyświetlania X jest już na starcie bardzo skomplikowany. Żeby zrobić coś tak prostego jak narysowanie kreski na ekranie, trzeba kolejno: otworzyć połączenie do serwera X; utworzyć okienko; zamapować je; utworzyć maskę notyfikacji; zaczekać na zdarzenie powiadamiające, że można rysować; wywołać właściwą funkcję rysującą kreskę; wyczyścić bufor połączenia (jak to opisuje ten tutorial). System czcionek bitmapowych, wiele sposobów określania współrzędnych ekranowych, wielki zestaw prymitywów graficznych, biblioteki Athena czy Xt — wszystko to jest bagażem historycznym, z którego współczesne oprogramowanie już nie korzysta, a mimo to każdy Linux przychodzi z całym tym dobrodziejstwem inwentarza. Dwa wzajemnie niekompatybilne podsystemy renderowania tekstu? Taka jest rzeczywistość. A próbowaliście kiedyś zapanować nad tym w czystym C? Powodzenia.

Do tego X przez lata obrastał w sadełko w postaci różnych rozszerzeń protokołu, jak XRandR czy XRENDER, potrzebnych do jako takiego sensownego działania. Współczesne pulpity z bajeranckimi efektami jak falujące okna czy choćby półprzezroczyste terminale nie byłyby bez nich możliwe.

Przezroczystość sieciowa, powiadacie? Tak, ale spróbujcie zdalnie uruchomić glxgears albo pograć w Quake'a po sesji X. Nawet Firefox, gdy go uruchamiam przez ssh -X po szybkim łączu na serwerze oddalonym o kilka kilometrów od domu, ślimaczy się i jest nieużywalny. Kwestię zdalnego uruchamiania aplikacji graficznych znacznie lepiej rozwiązał Microsoft ze swoim Remote Desktop.

Wayland ma szansę uprościć cały okołoiksowy bałagan. Jest napisany tak, by mógł wykorzystać sterowniki dla nowych kart graficznych, już istniejące dla X.org. Ma bardzo prosty model wyświetlania: to nie sam serwer wyświetla grafikę, ale po prostu udostępnia klientom swoje bufory. Współczesne biblioteki, jak Cairo, GTK+ czy Qt, już potrafią z niego korzystać. Chciałoby się powiedzieć: nareszcie!

Z niecierpliwością czekam dnia, w którym na żadnej z moich linuksowych maszyn nie będzie działał żaden serwer X.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1334473/nat-face-square.jpeg http://posterous.com/users/YXpC5IC5SCt Daniel Janus danieljanus Daniel Janus
Mon, 28 Feb 2011 03:06:24 -0800 Przeczytane: Sen elektryka (Wiesław Myśliwski, "Traktat o łuskaniu fasoli") http://plblog.danieljanus.pl/przeczytane-sen-elektryka-wieslaw-mysliwski-t http://plblog.danieljanus.pl/przeczytane-sen-elektryka-wieslaw-mysliwski-t

Mówi się czasem, że zapamiętujemy wszystko, co rejestrują nasze zmysły. Nawet jeśli wydaje się, że jakieś mało z pozoru znaczące zdarzenie z przeszłości odeszło w zapomnienie — bywa, że ni stąd ni zowąd przypominamy je sobie pod wpływem nowych zmysłowych doznań albo skojarzeń. I właśnie czegoś takiego ostatnio doświadczyłem, odłożywszy na półkę właśnie przeczytany “Traktat o łuskaniu fasoli” Wiesława Myśliwskiego.

To było jakieś cztery, może pięć lat temu; siedziałem wtedy w którymś ze stołecznych Sphinxów, jedząc obiad. Między jednym a drugim kęsem moje uszy wychwyciły znaną melodię, której zdecydowanie nie spodziewałem się usłyszeć w radiu “pod kotleta”. Pink Floyd. Który utwór Floydów może puszczać popularna rozgłośnia? “Money”? “Another Brick In The Wall, Part II”? Może od wielkiego dzwonu “Wish You Were Here”? Nie, to był utwór z jednego z najposępniejszych, najmniej podkotletowo wtapiających się w tło albumów (a przy okazji mojego ulubionego). “The Gunner’s Dream” z płyty “The Final Cut”.

Jest taki moment w tym utworze, kiedy przeciągłe “…and hold on to the dream” Watersa zanika przechodząc w długie solo saksofonowe. I właśnie wtedy usłyszałem ten saksofon inaczej. Do tej pory wydawało mi się, że harmonijnie współbrzmi z resztą, teraz jednak wybijał się, skrzeczał, zaznaczał się niespokojnym dysonansem. Mniej miło, ale może bardziej pasująco do nastroju utworu. Niepokój, niejasne wrażenie czegoś utraconego, co już nie istnieje. Ot, szybko wietrzejąca emocja, ale w uśpieniu przetrwała lata.

I obudziła się z ostatnią stroną “Traktatu…”.

Blurb na okładce mówi o “drążeniu Tajemnicy”, o “nieszukaniu łatwych rozwiązań”, o “metafizyce”. Może tak, ale nie to zapamiętam. Zwłaszcza że sama fabuła nie jest zawiła: oto u starszego pana zjawia się przybysz poszukujący fasoli do kupienia; fasolę ma ów pan tylko niełuskaną, więc zaprasza nieznajomego do domu i podczas całodziennego wspólnego łuskania snuje monolog – opowieść o sobie. O wspomnieniach z dzieciństwa, o rzece i lampie naftowej. O strasznych doświadczeniach wojny. O trudnych powojennych czasach odbudowy. O ciężkiej pracy elektryka i, tak, tak, o saksofonach. A także o kapeluszach i psach. Jest ten monolog bardzo naturalny i pozbawiony śladu pretensjonalności (i w tym tkwi jego siła: przywodzi mi na myśl równie mocne i proste “Requiem” Achmatowej), a mimo to zmusza do myślenia i zadawania pytań.

Jeśli przyjąć definicję prozy Włodka Holsztyńskiego (parafrazuję: proza to taki tekst literacki, w którym autor podaje 90% obrazu, zostawiając czytelnikowi zbudowanie sobie pozostałych 10%; ta definicja stawia prozę w opozycji do poezji, w której ta proporcja jest pół na pół — te procenty są oczywiście umowne, ale dają pewne wyobrażenie o roli czytelnika), to na tej skali “Traktat…” sytuuje się gdzieś pomiędzy. Bo jest niedopowiedziany, bo budzi wątpliwości, bo pozwala zajrzeć do niedookreślonego świata, który nie jest moim światem.

Ale podoba mi się. Bardzo polecam.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1334473/nat-face-square.jpeg http://posterous.com/users/YXpC5IC5SCt Daniel Janus danieljanus Daniel Janus
Sun, 13 Feb 2011 12:46:24 -0800 Janusowie z Dęblina? http://plblog.danieljanus.pl/janusowie-z-deblina http://plblog.danieljanus.pl/janusowie-z-deblina

Pisze do mnie Nan Cain z Florissant, Missouri (cytuję w tłumaczeniu):

Mój dziadek, Walenty Janus, urodził się w Polsce w roku 1890. Przybył do Stanów Zjednoczonych w młodym wieku, osiadł w stanie Michigan i ożenił się z Sophie Hojnacki. Próbuję dowiedzieć się czegoś więcej o jego rodzinie (być może jego ojciec miał na imię Franciszka [sic!]). W dokumentach imigracyjnych jako miejscowość pochodzenia figuruje Giblin, ale nie mogę znaleźć miejscowości w Polsce o tej nazwie.

Niewiele mi to mówi. Janus to dość popularne nazwisko; na stronie Marka Futregi widzę, że nosi je około 10 tys. osób w Polsce i akurat wśród znanych mi Janusów nie było żadnego Walentego, który by wyemigrował do USA. Wydaje mi się, że “Giblin” może być amerykańsko-fonetycznym zapisem Dęblina — tak mógł usłyszeć oficer imigracyjny i tak zapisać.

A publikuję ten post w nadziei, że może ktoś z czytelników tego bloga wie coś o Walentym i mógłby podzielić się informacją. Będę wdzięczny za wiadomość (najlepiej w komentarzach do niniejszego wpisu).

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1334473/nat-face-square.jpeg http://posterous.com/users/YXpC5IC5SCt Daniel Janus danieljanus Daniel Janus
Sat, 12 Feb 2011 13:14:14 -0800 Smyrna http://plblog.danieljanus.pl/smyrna http://plblog.danieljanus.pl/smyrna

Dziś chciałbym zaprezentować kolejny stworzony przeze mnie przykład wykorzystania Clojure w praktyce — program Smyrna. 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.

Zrzut ekranu

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ł łatwe przeszukiwanie własnych zbiorów polskich tekstów. Można wykorzystać Poliqarpa, 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 dxces i jest trudne do przeskoczenia dla nietechnicznych użytkowników. Jest więc Smyrna swoistym uzupełnieniem dla Poliqarpa — stąd zresztą nazwa.

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.

W ramach ćwiczenia, kliencki kod napisałem w nowej, alternatywnej składni dla JavaScriptu, czyli zyskującym coraz większą popularność CoffeeScripcie. Eksperyment uważam za udany: kod wychodzi czytelniejszy i zwięźlejszy niż w “zwykłym” JS.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1334473/nat-face-square.jpeg http://posterous.com/users/YXpC5IC5SCt Daniel Janus danieljanus Daniel Janus
Sun, 30 Jan 2011 16:32:01 -0800 Clojure: czyszczenie dowiązań lokalnych http://plblog.danieljanus.pl/clojure-czyszczenie-dowiazan-lokalnych http://plblog.danieljanus.pl/clojure-czyszczenie-dowiazan-lokalnych

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 locals clearing, które tłumaczę na polski jako “czyszczenie lokalnych dowiązań”.

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.

W Javie wyglądałoby to tak:

void calculate(Matrix m) {
    Matrix m1 = step1(m);
    Matrix m2 = step2(m1);
    // i tak dalej, każdy krok odwołuje się do poprzedniego
}

Jest to jednak prosta droga do pojawienia się OutOfMemoryError, o ile nie dysponujemy wystarczającą ilością pamięci, aby pomieścić wszystkie kroki. Gdy m2 jest obliczone, wartość m1 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 calculate nie zakończy się, ponieważ do tej pamięci odwołuje się ramka na stosie wywołań.

Zilustrujmy to uruchamialnym przykładem:

public class foo {
    public static byte[] calculate(byte[] lastStep) {
        return new byte[10485760];
    }

    public static void main(String... args) {
        byte[] a, b, c, d;
        a = calculate(null);
        b = calculate(a);
        c = calculate(b);
        d = calculate(c);
        System.out.println("OK");
    }
}

Funkcja calculate 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 (java -Xmx30M foo), zobaczymy błąd braku pamięci.

Inaczej jest w Clojure. Odpowiednik powyższego programu wygląda tak:

(defn calculate [last-step]
  (make-array Byte/TYPE 10485760))

(defn -main [& args]
  (let [a (calculate nil)
        b (calculate a)
        c (calculate b)
        d (calculate c)]
    (println "OK")))

W Clojure 1.2 i nowszych ten program wypisze OK, nawet gdy będzie miał do dyspozycji tylko 30 MB pamięci.

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 let), 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 null). Innymi słowy, efekt jest taki, jakbyśmy w przykładowym kodzie w Javie wstawili instrukcję a = null; zaraz po obliczeniu b i analogicznie dalej.

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ą.

Jak jednak napisał mi Rich Hickey, sama alokacja pamięci w momencie wiązania nazwy nie jest 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 let. Dlatego też nie mogłem pominąć w powyższym przykładzie argumentu last-step funkcji calculate. 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ć!

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1334473/nat-face-square.jpeg http://posterous.com/users/YXpC5IC5SCt Daniel Janus danieljanus Daniel Janus
Wed, 19 Jan 2011 00:58:00 -0800 Pokoloruj sobie Europę! http://plblog.danieljanus.pl/pokoloruj-sobie-europe http://plblog.danieljanus.pl/pokoloruj-sobie-europe

To jest druga część minicyklu zapoczątkowanego artykułem o zipperach. Temat ten zaprezentowałem również podczas wystąpienia na spotkaniu Warszawa JUG. Dostępne są slajdy z tej prezentacji (uwaga: slajdy mają postać oskryptowanego HTML; należy je oglądać w Firefoksie lub którejś przeglądarce WebKitowej). Można również obejrzeć nagranie wideo wystąpienia, niestety ze słabą jakością dźwięku.

Problem

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:

Pokolorowana mapa Europy

Rozpocząłem od ściągnięcia łatwo edytowalnej mapy ze stron Wikimedia Commons, obliczyłem potrzebne intensywności kolorów dla pierwszego zestawu, uruchomiłem Inkscape 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.

SVG

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 path, wyglądającym mniej więcej tak:

<path
   id="pl"
   class="eu europe"
   d="współrzędne kolejnych węzłów krzywej (bardzo dużo)" />

Warto zwrócić uwagę na atrybut id — 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ę.

Jak się okazuje, SVG, podobnie jak HTML, używa stylów CSS do definiowania wyglądu elementów. Wszystko, co trzeba zrobić do nadania Polsce koloru czerwonego, to nadanie odpowiedniej wartości atrybutowi stylu fill:

<path
   id="pl"
   style="fill: #ff0000;"
   class="eu europe"
   d="współrzędne kolejnych węzłów krzywej (bardzo dużo)" />

Uzbrojeni w taką wiedzę, możemy rozpocząć pracę.

XML w Clojure

Podstawowym sposobem obsługi XML w Clojure jest wykorzystanie biblioteki clojure.xml, 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:

> (use 'clojure.xml)
nil
> (def m (parse "/home/nathell/eur/Blank_map_of_Europe.svg"))
[...dłuuugie oczekiwanie...]
Unexpected end of file from server
  [Thrown class java.net.SocketException]

Zaraz! Jaki znowu SocketException? Firefox wyświetla tę mapkę poprawnie, Chromium też, WTF? W tak świetnym języku, jakim jest Clojure, wszystko powinno działać od ręki?!

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 javax.xml) parsera XML w Javie. Jest on restrykcyjny i stara się odrzucać dokumenty, które nie są poprawne (w sensie valid, a nie tylko well-formed). Jeśli tylko dany plik zawiera deklarację DOCTYPE, to parser javowy, a za nim clojure.xml/parse, 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 World Wide Web Consortium, 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 post na blogu W3C. 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ę DOCTYPE.

> (def m (parse "/home/nathell/eur/bm.svg"))
#'user/m
> m
[...wiele ekranów liczb...]

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:

> (keys m)
(:tag :attrs :content)

Jest to więc mapa o trzech polach: z nazw (albo z dokumentacji) można się domyślić, co zawierają. Pole :tag to nazwa elementu XML, :attrs jest mapą atrybutów tego elementu, a content — 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):

> (:tag m)
:svg
> (:attrs m)
{:xmlns "http://www.w3.org/2000/svg", :width "680", :height "520", :viewBox "1754 161 9938 7945", :version "1.0", :id "svg2"}
> (count (:content m))
68

Tak dla wprawki, spróbujmy zapisać sparsowaną reprezentację z powrotem jako XML. Funkcja emit powinna być w stanie to zrobić, ale wypisujekl ona XML na standardowe wyjście. Możemy użyć makra with-out-str z pakietu clojure.contrib.io, aby zrzucić XML-a do pliku:

> (use 'clojure.contrib.io)
nil
> (with-out-writer "/tmp/a.xml" (emit m))
nil

Próbujemy otworzyć wynikowy obrazek w Firefoksie i…

Błąd parsowania XML: nieprawidłowo sformowany
Obszar: file:///tmp/a.xml
Numer linii: 15, kolumna 44: Updated to reflect dissolution of Serbia & Montenegro: http://commons.wikimedia.org/wiki/User:Zirland
-------------------------------------------^

Argh! Wygląda na to, że znaleźliśmy błąd w clojure.xml: 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.

Kolorujemy Polskę

Widzieliśmy wcześniej, że główny element XML w naszym pliku zawiera 68 podelementów. Zobaczmy, jakie one są — wystarczą nam tagi:

> (map :tag (:content m))
(:title :desc :defs :rect :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :path :g :path :path :g :path :path :path)

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ę:

> (count (filter #(and (= (:tag %) :path) 
                       (= ((:attrs %) :id) "pl")) 
                 (:content m)))
1

(Ten kawałek kodu wybiera z listy podelementów m takie, których tag to path, a wartość atrybutu id to "pl" i zwraca długość takiej podlisty). Spróbujmy teraz dodać do tego elementu atrybut style, 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 m, z tym, że odpowiedniemu podelementowi ustawimy styl:

> (def m2 (assoc m
                :content
                (map #(if (and (= (:tag %) :path)
                               (= ((:attrs %) :id) "pl"))
                        (assoc % :attrs (assoc (:attrs %) :style "fill: #ff0000;"))
                        %)
                     (:content m))))
#'user/m2
> (with-out-writer "/tmp/a.svg" (emit m2))
nil

Otwieramy utworzony plik i widzimy mapkę z Polską zaznaczoną na czerwono. Hura!

Generalizacja

Uogólnimy trochę nasz kod. Napiszemy funkcję, która pokoloruje jedno państwo, dostając na wejściu element path (podelement svg):

(defn color-state
  [{:keys [tag attrs] :as element} colorize-fn]
  (let [state (:id attrs)]
    (if-let [color (colorize-fn state)]
      (assoc element :attrs (assoc attrs :style (str "fill:" color)))
      element)))

Jest to funkcja bardzo podobna do tej anonimowej, użytej powyżej w wywołaniu map, 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 tag i attrs: więcej o takiej notacji, zwanej destructuring (“dzielenie struktury”) można przeczytać w odpowiedniej części dokumentacji Clojure), a drugim… funkcja, która dostanie jako argument dwuliterowy kod państwa i zwróci HTML-owy opis jego koloru (lub nil, jeśli kolor dla tego państwa nie jest określony — color-state poradzi sobie z tym i zwróci element w stanie nienaruszonym).

Mając color-state, możemy łatwo napisać funkcję wyższego poziomu, która w jednym kroku przetworzy i zapisze XML:

(defn save-color-map
  [svg colorize-fn outfile]
  (let [colored-map (assoc svg :content (map #(color-state % colorize-fn) (:content svg)))]
    (with-out-writer out
      (emit colored-map))))

Testujemy:

> (save-color-map m {"pl" "#00ff00"} "/tmp/a.svg")
nil

Tym razem Polska jest zielona (jako argumentu color-state użyliśmy mapy kraj→kolor, bo mapy są w Clojure wołalne jak funkcje). Spróbujmy dorzucić niebieskie Niemcy:

> (save-color-map m {"pl" "#00ff00", "de" "#0000ff"} "/tmp/a.svg")
nil

Działa!

Problem z Wielką Brytanią

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:

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)

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 wszystkie definicje krajów są bezpośrednimi podelementami path, okazuje się fałszywe. Trzeba to naprawić.

Dotychczas robiliśmy “płaską” transformację drzewa SVG: zamienialiśmy wszystkie podelementy elementu głównego i nic głębiej. Trzeba by zmienić wszystkie elementy path (i g, jeśli chcemy kolorować grupy takie jak Wielka Brytania), niezależnie od tego, jak głęboko siedzą w drzewie. Z pomocą przychodzi funkcja map-zipper z poprzedniego odcinka.

Zważywszy, że funkcja clojure.xml/zip-xml zwraca zipper działający na drzewach XML-owych, przepisujemy save-color-map jako:

(defn save-color-map
  [svg colorize-fn outfile]
  (let [colored-map (map-zipper #(color-state % colorize-fn) (fn [x] (#{:g :path} (:tag x))) (zip/xml-zip svg))]
    (with-out-writer out
      (emit colored-map))))

Tym razem Wielka Brytania już daje się pomalować.

Koloryzatory

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ą koloryzator, czyli funkcję nadającą się do przekazania jako argument do color-state i save-color-map (dotąd używaliśmy w tym celu map).

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:

(defn htmlize-color
  [[r g b]]
  (format "#%02x%02x%02x" r g b))

Wstawmy jeszcze wywołanie htmlize-color w odpowiednim miejscu w color-state:

(defn color-state
  [{:keys [tag attrs] :as element} colorize-fn]
  (let [state (:id attrs)]
    (if-let [color (colorize-fn state)]
      (assoc element :attrs (assoc attrs :style (str "fill:" (htmlize-color color))))
      element)))

Wyobraźmy sobie teraz, że mamy tabelkę z wartościami liczbowymi dla państw, np. taką:

Państwo   Wartość
------------------
Polska    20
Niemcy    15
Holandia  30

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).

Brzmi to zagmatwanie, ale mam nadzieję, że na przykładzie będzie widać lepiej. Tak wygląda implementacja clojurowa powyższej funkcji:

(defn make-colorizer
  [dataset ranges]
  (let [minv (apply min (vals dataset))
        maxv (apply max (vals dataset))
        progress (map (fn [[min-col max-col]] (/ (- max-col min-col) (- maxv minv))) ranges)]
    (into {}
          (map (fn [[k v]] [(.toLowerCase k) (map (fn [progress [min-color _]] (int (+ min-color (* (- v minv) progress)))) progress ranges)])
               dataset))))

Zobaczmy, jak działa na naszych przykładowych danych:

> (make-colorizer {"pl" 20, "de" 15, "nl" 30} [[0 255] [0 0] [0 0]]) 
{"pl" (85 0 0), "de" (0 0 0), "nl" (255 0 0)}

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).

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).

Podsumowanie

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).

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 GitHub. Forki będą bardzo mile widziane.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1334473/nat-face-square.jpeg http://posterous.com/users/YXpC5IC5SCt Daniel Janus danieljanus Daniel Janus
Thu, 13 Jan 2011 14:56:59 -0800 Pierwsze użycie: protokoły i git-bisect http://plblog.danieljanus.pl/pierwsze-uzycie-protokoly-i-git-bisect http://plblog.danieljanus.pl/pierwsze-uzycie-protokoly-i-git-bisect

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 Fablo.

Fablo to napisany w Clojure 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”.

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.

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 protokoły — nowy element języka wprowadzony w Clojure 1.2.

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 deftype), 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 java.lang.Integer i java.lang.String do implementowania interfejsu Blabalize; możemy wołać odpowiednią funkcję bezpośrednio na obiektach odpowiednich klas, bez żadnych wrapperów!

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.

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ć.

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 Git do kontroli wersji, wykorzystałem narzędzie git-bisect, 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:

#!/bin/bash
cake clean
cake deps
cake proto
sleep 10
cake release
rm *.log
make test
if grep -q "PODEJRZANY_KOMUNIKAT" fablo*log; then
  exit 1
else
  exit 0
fi

(Cake jest narzędziem, którego używamy do budowania oprogramowania; 10-sekundowa przerwa między cake proto a cake release jest remedium na błąd w cake.)

Potem wystarczyło tylko:

$ git bisect start HEAD dobry_commit --
$ git bisect run check.sh

i już wiedziałem, w którym miejscu się zepsuło, a stąd było już niedaleko do znalezienia przyczyny problemu.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1334473/nat-face-square.jpeg http://posterous.com/users/YXpC5IC5SCt Daniel Janus danieljanus Daniel Janus
Thu, 25 Nov 2010 14:34:33 -0800 Zippery w Clojure http://plblog.danieljanus.pl/zippery-w-clojure http://plblog.danieljanus.pl/zippery-w-clojure

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).

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ł książki M. Fogusa i C. Housera.

A dziś opowiem o ciekawej strukturze danych, jaką są zippery. (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?

Zipper jest strukturą danych, dającą iluzję imperatywności przy manipulowaniu drzewami. Pamiętamy, że Clojurowe natywne struktury danych są niezmienne (immutable): 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 nową listę, która będzie zawierać wszystkie elementy listy wyjściowej i jeszcze jakiś.

Niezmienność jest bardzo przydatna (o filozoficznych podstawach takiego podejścia, które zadecydowały o jego wykorzystaniu w Clojure, można poczytać w artykule “On State and Identity”), ale wymusza myślenie w innych kategoriach. To jest to, co czasami nazywa się “myśleniem funkcyjnym”: zamiast zastanawiać się, w jaki sposób zmienić wartość naszej zmiennej, aby doprowadzić ją do pożądanego stanu, pytamy o to, jak z jednych wartości robić inne. 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 modus operandi. Bywa jednak i tak, że o danym problemie imperatywnie myśli się wygodniej niż funkcyjnie. Tu właśnie wkraczają zippery.

dodawanie do drzewa

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?

  • Zejdź dwa razy w dół w prawo (“dobierz się” do węzła 6).
  • Wstaw czwórkę na lewo od bieżącego węzła.

I już. A teraz funkcyjnie — pamiętamy, że z drzewa robimy nowe drzewo:

  • 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.

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:

  • Stwórz zipper na podstawie drzewa wyjściowego.
  • Wykonaj na tym zipperze operację “zejdź w dół”, otrzymując nowy zipper.
  • Wykonaj na tym zipperze operację “przejdź w prawo”, otrzymując nowy zipper.
  • Wykonaj na tym zipperze operację “zejdź w dół”, otrzymując nowy zipper.
  • Wykonaj na tym zipperze operację “wstaw 4 przed bieżącym elementem”, otrzymując nowy zipper.
  • Wykonaj na tym zipperze operację “daj drzewo wynikowe”, otrzymując pożądane drzewo.

Clojure zawiera implementację zipperów w bibliotece standardowej (w przestrzeni nazw clojure.zip). Oto jak można by zapisać powyższy przykład w Clojure:

(def nowe-drzewo 
  (let [z1 (zip drzewo)
        z2 (down z1)
        z3 (right z2)
        z4 (down z3)
        z5 (insert-left z4 4)]
    (root z5)))

(tu explicite nazywam kolejne kroki obliczeń). Albo tak, używając makra ->:

(def nowe-drzewo 
  (-> drzewo 
      zip
      down
      right
      down
      (insert-left 4)
      root)))

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.

Rozwiązując mój problem (na razie nie zdradzam, jaki), natknąłem się na potrzebę posiadania funkcji, która działałaby jak map, 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 true 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ć.

Tak się szczęśliwie składa, że Clojurowe zippery mają funkcję next, która spaceruje po drzewie “w głąb”. Mając je, mogłem pomyśleć tak o swojej implementacji:

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”.

Tłumacząc to na Clojure, dostajemy:

(defn map-zipper [f pred z]
  (if (zip/end? z)
    (zip/root z)
    (recur f pred (-> z (zip/edit #(if (pred %) (f %) %)) zip/next)))))

I testujemy:

> (map-zipper inc integer? 
    (zip/vector-zip [1 [2 [3 4]] [5] [6 [[7 8 9]]]]))
[2 [3 [4 5]] [6] [7 [[8 9 10]]]]

Trzeba podać predykat integer?, 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 map-zipper:

(map-zipper #(do (println %) %) 
            (constantly true) 
            (zip/vector-zip [1 [2 [3 4]] [5] [6 [[7 8 9]]]]))
[1 [2 [3 4]] [5] [6 [[7 8 9]]]]
1
[2 [3 4]]
2
[3 4]
3
4
[5]
5
[6 [[7 8 9]]]
6
[[7 8 9]]
[7 8 9]
7
8
9
[1 [2 [3 4]] [5] [6 [[7 8 9]]]]

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1334473/nat-face-square.jpeg http://posterous.com/users/YXpC5IC5SCt Daniel Janus danieljanus Daniel Janus
Thu, 18 Nov 2010 17:46:57 -0800 Filmujmy błędy! http://plblog.danieljanus.pl/filmujmy-bledy http://plblog.danieljanus.pl/filmujmy-bledy

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ą.

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 Joel Spolsky, to odpowiedzi na pytania: co powinno było się stać oraz co naprawdę się stało.) 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.

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 Jak efektywnie zgłaszać błędy, 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.

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ł.

Od jakiegoś czasu w Fablo dołączamy do zgłoszeń błędów krótkie filmiki — screencasty 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 heisenbugiem (“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ć.

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 Snapz Pro X na Mac OS X nagranie jest tak proste jak naciśnięcie Shift-Cmd-3 na początku i na końcu filmu; Record My Desktop na Linuksa i FreeBSD jest nieco trudniejszy w obsłudze, ale wciąż łatwy.

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, nolens volens, trzeba opisać.

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 open source. 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 FogBugz 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 Dropbox.

Filmujmy więc błędy!

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1334473/nat-face-square.jpeg http://posterous.com/users/YXpC5IC5SCt Daniel Janus danieljanus Daniel Janus
Sun, 14 Nov 2010 21:10:59 -0800 Wiersze nieprzetłumaczalne: Robert Browning, “Toccata Galuppiego” http://plblog.danieljanus.pl/wiersze-nieprzetlumaczalne-robert-browning-to http://plblog.danieljanus.pl/wiersze-nieprzetlumaczalne-robert-browning-to

“Nie ma wierszy nieprzekładalnych, są tylko leniwi tłumacze”. Taki aforyzm nieznanego autorstwa cytuje Stanisław Barańczak w “Ocalonym w tłumaczeniu”, zastrzegając się, że nie jest pewien jego słuszności. I choć Barańczak kontestuje istnienie takich wierszy, których on sam nie potrafiłby na polski przełożyć, nieprzekładalności dopatrując się raczej w tłumaczeniach w drugą stronę — ja od czasu do czasu, zastanawiając się nad tym, jak po polsku mógłby brzmieć ten lub inny wiersz, dochodzę do wniosku, że dobrze przetłumaczyć się go nie da. Dziś przykład: Toccata Galuppiego (A Toccata of Galuppi’s) Roberta Browninga, wiersz z roku 1855 ze zbioru “Women and Men”.

Posłuchajmy pierwszej zwrotki:

Oh, Galuppi, Baldassaro, this is very sad to find!
I can hardly misconceive you; it would prove me deaf and blind;
But although I take your meaning, ‘tis with such a heavy mind!

Mówię “posłuchajmy”, a nie “popatrzmy” albo “przeczytajmy”, bo to jeden z tych wierszy, które brzmią; w nim melodia tekstu jest co najmniej równie ważna, jak jego dosłowny sens. Napisałem tak, jak gdyby dało się oddzielić te dwie rzeczy, ale to nieprawda. Ten wiersz jest autotematyczny: jest o muzyce i zarazem jest muzyką. I to taką, która jest w stanie przenieść słuchacza w nigdy niewidziane krainy z całym ich bogactwem, tak, aby czuł tam swoją obecność niemal namacalnie. I właśnie przeniesienie tej muzyki w obszar naszego języka wydaje mi się niemożliwe do urzeczywistnienia.

Jak bowiem mógłby wyglądać polski przekład? Jak musiałby oddać różne aspekty oryginału?

Po pierwsze — rytm. W oryginale jest to peon trzeci z kataleksą, w którym każdy wers składa się z czterech stóp. Tak przynajmniej ja to słyszę; można też to traktować jako ośmiostopowiec trocheiczny (tak podobno pisał o nim sam Browning w liście do F. J. Furnivalla — podaję za wydaniem z komentarzami pod redakcją Woolforda i Karlina), ale moim zdaniem pierwsze akcenty w każdej parze trochejów są znacznie słabsze niż drugie. Jednak ta subtelna różnica jest mało istotna wobec pytania, jaki rytm wybrać dla polskiej wersji?

Już pierwszy półwers narzuca odpowiedź. Inwokacja do kompozytora wydaje się jednym z tych fragmentów, które trzeba pozostawić nienaruszone na swoim miejscu, a więc początek brzmiałby jakoś “O Galuppi, Baltazarze!” albo jakoś podobnie. Tak samo jak w oryginale, dwa peony, po których następuje średniówkowy oddech, pauza. A jeśli średniówka, to po niej drugi półwers powinien mieć mniej więcej tę samą długość, może skróconą o jedną sylabę. Do rozważenia są więc tylko dwa warianty:

  • czterostopowiec peoniczny (bez kataleksy, z rymami żeńskimi),
  • czterostopowiec peoniczny z kataleksą, tak jak w oryginale.

Pierwszy wariant byłby łatwiejszy, bo z rymami żeńskimi jest w polszczyźnie znacznie mniejszy problem niż z męskimi (w każdej zwrotce, a jest ich piętnaście, trzeba znaleźć rym potrójny i to niebanalny!) Jednak wystarczy sobie to wystukać na stole, żeby usłyszeć, dlaczego tak się nie da:

puk puk PUK puk / puk puk PUK puk / puk puk PUK puk / puk puk PUK puk
puk puk PUK puk / puk puk PUK puk / puk puk PUK puk / puk puk PUK puk

Słychać? Katarynka. Wciąż ten sam puk-puk-PUK-puk przez sto osiemdziesiąt iteracji (3 * 15 * 4). Po dwudziestu paru razach można usnąć; przykuwający uwagę rytm oryginału, z dobitnymi ostatnimi sylabami, gdzieś uciekł.

A więc tak też nie. Rytm musi być dokładnie taki jak w oryginale, co skazuje nas na rymy męskie, z którymi, jako się rzekło, jest problem: słów jednosylabowych jest w języku polskim za mało. W “Oficjalnym słowniku polskiego scrabblisty” można znaleźć ich kilka tysięcy (na ponad dwa miliony wszystkich odnotowanych form słów):

$ egrep '^[^aeioóuyąę]*(y|i?[aeouóąę])[^aeioóuyąę]*$' osps.txt | wc -l
5975

Z tego przydatnych i możliwych do użycia w nieudziwnionym języku jest zapewne nie więcej niż tysiąc; reszta to dziwolągi w stylu angst, alb i agf (wybrałem parę na chybił trafił z pierwszej dziesiątki). Wybranie z tego tysiąca piętnastu wzajemnie rymujących się trójek, stanowiących w dodatku trafne ekwiwalenty kawałków oryginału, wydaje się graniczyć z niemożliwością.

Nawet gdyby się to jednak udało, pozostaje kwestia naturalnej zwięzłości języka angielskiego, której nie ma polszczyzna. Jest w “Toccacie” wiele półwersowych fraz składających się tylko z jednosylabowych słów i brzmiących bardzo naturalnie, jak choćby “It’s as if I saw it all.” Jak powiedzieć po polsku w siedmiu sylabach “Tak, jak gdybym to wszystko widział”? Albo “Venice spent what Venice earned” — dwukrotne powtórzenie nazwy Wenecji jest tu bardzo znaczące, ale jedno z nich trzeba by w polskiej wersji wyrzucić, żeby mieć niezerową ilość miejsca, a i tak będzie bardzo trudno dobrze oddać tę frazę. Albo te wielokrotne echolalie, “those suspensions, those solutions”, “dear dead women”…

Nie, moim zdaniem się nie da. Wsłuchajmy się w bicie dzwonów bazyliki św. Marka, rozbrzmiewające dobitnie w oryginale.

Na zakończenie wspomnę, że cytowany tu fragment pochodzi z późniejszej, częściej spotykanej redakcji wiersza. W pierwszej wersji, tej z Women and Men, trzecia linijka brzmiała:

But although I give you credit, ‘tis with such a heavy mind!

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1334473/nat-face-square.jpeg http://posterous.com/users/YXpC5IC5SCt Daniel Janus danieljanus Daniel Janus
Wed, 10 Nov 2010 17:09:00 -0800 Leniwa wersja makra -> http://plblog.danieljanus.pl/leniwa-wersja-makra http://plblog.danieljanus.pl/leniwa-wersja-makra

Jacek Laskowski podaje ciekawy przykład wykorzystania monad w Clojure — aplikowanie kolejnych funkcji do wyrażenia, dopóki ma ono wartość nie będącą nil. Przykład mi się podoba, bo jest prosty, ale nie trywialny — wykorzystuje monadę maybe 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 tym wątku.

Nie mogę powiedzieć, że rozumiem monady. Owszem, znam definicję i potrafię napisać program w Haskellu wykorzystujący monadę IO, 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.

Jacek tak postawił oryginalny problem:

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”.

Moje pierwotne rozwiązanie jest po prostu złożeniem trzech funkcji:

(defn pracownik->waluta [p] 
  (-> p pracownik->departament departament->kraj kraj->waluta))

O ile wszystkie z nich są mapami (pamiętamy, że w Clojure mapy implementują interfejs IFn, można je więc uważać za funkcje i wołać jak funkcje), to dla żadnej wartości wejściowej nie zostanie rzucony NullPointerException, ponieważ wywołanie mapy dla nieistniejącego klucza zwraca nil.

Jacek słusznie zauważa jednak, że wykonujemy w ten sposób więcej pracy niż trzeba. Jeżeli już w mapie pracownik->departament nie ma departamentu dla danego pracownika, to otrzymany nil zostanie “przepchnięty” przez pozostałe dwie mapy, zanim będzie zwrócony. Można kontrargumentować, że taka implementacja pracownik->waluta 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?

Odpowiedzią jest makro, które nazwałem and->. Działa ono tak samo, jak ->, z tą różnicą, że kolejne wartości oblicza tylko jeśli po drodze nie pojawiło się false lub nil, podobnie jak and (stąd nazwa). Oto ono:

(defmacro and-> 
  ([x] x)
  ([x form] `(when-let [y# ~x] (~form y#)))
  ([x form & more] `(when-let [y# (and-> ~x ~form)] 
                      (and-> y# ~@more))))

Implementacja jest bardzo zbliżona do zwykłego ->, którego kod w Clojure 1.2 wygląda tak. W stosunku do -> moje makro jest dla większej poglądowości trochę uproszczone i nie obsługuje konstrukcji typu (and-> mapa (get :klucz)), które normalnie rozwijane są do (get mapa :klucz).

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ą when-let. 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 rozdziale 8 książki “Practical Common Lisp”.

Aktualizacja [7.12.2010]

Jak się okazuje, clojure-contrib zawiera już takie makro! Nazywa się -?> i można je znaleźć w przestrzeni nazw clojure.contrib.core. 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ć.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1334473/nat-face-square.jpeg http://posterous.com/users/YXpC5IC5SCt Daniel Janus danieljanus Daniel Janus
Sat, 06 Nov 2010 03:27:00 -0700 O łącznikach i myślnikach w metrze, czyli dlaczego typografii powinno się uczyć w szkołach http://plblog.danieljanus.pl/o-lacznikach-i-myslnikach-w-metrze-czyli-dlac http://plblog.danieljanus.pl/o-lacznikach-i-myslnikach-w-metrze-czyli-dlac

Lubię ekrany informacyjne zainstalowane w metrze warszawskim, tak na stacjach, jak i w pociągach; pozwalają nie nudzić się, gdy akurat nie mam pod ręką niczego innego do czytania. Jednak treść prezentowana na tych wyświetlaczach pozostawia często wiele do życzenia, także pod względem jej prezentacji.

Metro

Przykład można zobaczyć na powyższym zdjęciu. Rzuca się w oczy błąd typograficzny: w funkcji myślnika, a więc otoczonego odstępami znaku oddzielającego dwie odrębne części myśli zawartej w wypowiedzeniu, użyty jest tu dywiz (-) zamiast dłuższej pauzy (—) lub półpauzy (–).

Na Wikipedii można znaleźć dobre omówienie różnic między tymi znakami, nie będę się więc rozpisywał na ten temat.

To częsty błąd, i ktoś mógłby powiedzieć, że się czepiam. Zapewne tak; jestem wyczulony na tego typu pomyłki. Gdy widzę taki błąd na czyjejś prywatnej stronie internetowej, przechodzę nad nim do porządku dziennego. Gdybym wiedział, że to jednorazowa wpadka i w innych artykułach znaki interpunkcyjne stosowane są poprawnie — zmełłbym w ustach przekleństwo. Jest jednak inaczej. Za każdym razem, kiedy na ekranach metra powinna pojawić się gdzieś pauza, widzę w tym miejscu dywiz. Uważam to za niechlujstwo godne publicznego piętnowania.

Te komunikaty są dziennie czytane przez dziesiątki tysięcy osób. Błąd widziany wielokrotnie przenika w końcu do podświadomości, stając się złym nawykiem. Ten sam zarzut można zresztą podnieść w stosunku do stron internetowych Gazety Wyborczej, z której — jak widać — pochodzi notka ze zdjęcia.

Zapytajmy jednak, skąd wziął się błąd? Zapewne osoby przygotowujące notki mają tyle pracy, że nie mają czasu na typograficzną redakcję. Tego typu praca może być jednak z powodzeniem wykonywana automatycznie, przez oprogramowanie wyświetlające artykuły. Każdy procesor tekstu zamienia otoczone spacjami łączniki na myślniki w czasie pisania; dla stron internetowych istnieje SmartyPants i jego odpowiedniki. Czasy, kiedy taka zamiana była utrudniona technicznie, minęły wraz z upowszechnieniem się Unikodu. Szkoda, że tu tego zabrakło.

A zabrakło dlatego, że świadomość typograficzna w polskim społeczeństwie (a więc i u autora oprogramowania) jest niska. Uczymy się w szkole, że nie należy zostawiać jednoliterowych słów na końcach linijek (por. wystające w na zdjęciu), że na początku akapitów robi się wcięcia, że marginesy są istotne — i to wszystko. Ta wiedza wystarcza do zapewnienia estetyki tekstów pisanych ręcznie, ale w dobie komputerów i Internetu to nie wszystko. Kiedyś zdecydowana większość tekstu drukowanego, z jakim mieli styczność ludzie, składana była przez zawodowych zecerów. Dziś każdy może wydrukować cokolwiek na drukarce, ale nie każdy potrafi sprawić, aby wynik jego pracy był estetyczny.

Podstawy typografii powinny być nauczane w szkole podstawowej. Nie trzeba do tego osobnego przedmiotu; wystarczy kilka godzin na lekcjach polskiego lub plastyki. Gdyby więcej osób znało różnicę między cudzysłowem a znakiem cala, świat stałby się ładniejszy.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1334473/nat-face-square.jpeg http://posterous.com/users/YXpC5IC5SCt Daniel Janus danieljanus Daniel Janus
Fri, 05 Nov 2010 16:51:00 -0700 Zaczynamy http://plblog.danieljanus.pl/zaczynamy http://plblog.danieljanus.pl/zaczynamy

Jeszcze jeden blog? Nie dość było jednego, i tak nieczęsto uaktualnianego? A jednak.

Od jakiegoś czasu prowadzę nieregularny blog po angielsku. Zbierając pomysły na notki zauważyłem, że część z nich ma charakter trochę hermetyczny, raczej niezrozumiały dla szerokiej, angielskojęzycznej publiczności. Teksty, które dotyczą lokalnych, warszawskich spraw albo języka polskiego, nie nadawałyby się na angielskiego bloga. Pomyślałem też, że pisząc po polsku o Clojure mógłbym przyczynić się do popularyzacji tego języka wśród polskich programistów.

Jest to też dobra okazja do wypróbowania nowej platformy blogowej. O istnieniu Posterous wiedziałem od dawna, ale nie byłem świadom rozlicznych ułatwień, jakie oferuje, a do których się przyzwyczaiłem pisząc starego bloga. Zobaczymy, jak się sprawdzi. Kusi mnie, żeby się rozpisać więcej, ale nie chcę, by ta pierwsza notka przerodziła się w długie rozważania na temat zalet i wad systemów blogowych; poużywam trochę i napiszę osobną notkę na ten temat.

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1334473/nat-face-square.jpeg http://posterous.com/users/YXpC5IC5SCt Daniel Janus danieljanus Daniel Janus