29. července 2011

Jak měnit neměnitelné. Refs

Clojure používá neměnitelná (immutable) data/objekty. Pokud potřebujeme měnitelná (mutable) data, řeší to Clojure "měnitelnou referencí na neměnitelný objekt" :-) Jedním z prostředků, které toto řeší jsou Refs: jsou to transakční reference, které umožňují bezpečné sdílení měnitelných úložišť pomocí systému STM (Software Transactional Memory).

Měnitelná reference (ref) se vytvoří funkcí ref, její hodnotu vrací buď funkce deref, nebo makro @:
(def my-ref (ref "immutable data"))
; #'user/my-ref
my-ref
; #<ref@18352d8: "immutable data">
@my-ref
; "immutable data"
(deref my-ref)
; "immutable data"
Pokud chceme referenci nastavit na jinou (neměnitelnou) hodnotu, slouží k tomu funkce ref-set:
(ref-set my-ref "another immutable data")
; IllegalStateException No transaction running  clojure.lang.LockingTransaction.getEx (LockingTransaction.java:208)
Jejda! Zapomněli jsme na transakci :-)
(dosync (ref-set my-ref "another immutable data"))
; "another immutable data"
@my-ref
; "another immutable data"
Pokud chceme provést čtení hodnoty a zároveň její změnu v jednom kroku (= aplikovat na hodnotu funkci), je vhodné použít funkci alter:
(dosync (alter my-ref #(apply str (reverse %))))
; "atad elbatummi rehtona"
@my-ref
; "atad elbatummi rehtona"
Na reference je také možné přidat validace:
(def counter (ref 0 :validator number?))
; #'user/counter
(dosync (ref-set counter "string"))
; IllegalStateException Invalid reference state  clojure.lang.ARef.validate (ARef.java:33)
@counter
; 0
(dosync (ref-set counter 42))
; 42
@counter
; 42
O Refs jsem už jednou (trochu) psal. Z dnešního pohledu k tomu mám dvě výhrady:
  • místo ref-set jsem měl použít alter,
  • místo reference jsem měl použít atom.
ref-set je vhodné použít tehdy, pokud přiřazuji novou hodnotu (nepočítám ji). alter tehdy, pokud nad hodnotou provádím nějakou funkci (např. inkrementace, přidání hodnoty do kolekce apod.).

Při rozhodování, jestli použít ref nebo atom je podstatné, jestli využiju transakci - v transakci můžu updatovat více referencí. Pokud budu měnit pouze jedinou hodnotu (bez vazby na cokoli jiného) je vhodnější použít atom (a o těch až někdy příště).

24. července 2011

Nekonečná lenost sekvencí

Velmi silnou (a zajímavou) zbraní Clojure jsou sekvence (neboli seq, čti [si:k]). Sekvence je logický seznam, který implementuje jednoduché rozhraní ISeq a umožňuje sekvenční přístup k datům - a to nejenom k těm, u kterých bychom to čekali (kolekce = seznamy, mapy, apod.), ale i k těm, kde potřebné sekvenční implementační detaily chybí, např. stromové struktury (XML, adresáře), databázové result sety  či textové soubory (buď jeden velký řetězec, nebo vektor řádků), I/O streamy, anebo obyčejné znakové řetězce (String).

Věc, na kterou bych se chtěl podívat je jednak lenost (laziness) a jednak (možná) nekonečnost (infiniteness) sekvencí. Laziness je známá např. z databázového/ORM světa, kdy se lazy typicky dotahují data v 1:N vztazích. Výhodou lazy sekvencí je:

  • odsunutí výpočtu, který možná nebude potřeba,
  • možnost práce s velkými daty, která se nevejdou do paměti,
  • odsunutí I/O operací, až budou opravdu potřeba.

Co se týká nekonečnosti, tam je to jasné - některé sekvence prostě jsou nekonečné: přirozená čísla, prvočísla, Fibonacciho posloupnost, atd.

Ukázkový příklad jsem převzal z knížky Programming Clojure a sice protože mi přišel tak dobrý, že se mi zdálo zbytečné vymýšlet příklad vlastní (ach ta lenost :-). Zajímá vás, jak vypadá milionté prvočíslo?
(use '[clojure.contrib.lazy-seqs :only (primes)])
; nil
(def ordinals-and-primes
  (map vector (iterate inc 1) primes))
; #'user/ordinals-and-primes
(first (drop 999999 ordinals-and-primes))
; [1000000 15485863]

Pro vysvětlení, Var primes obsahuje lazy sekvenci prvočísel, Var ordinals-and-primes obsahuje dvojice hodnot [pořadové-číslo prvočíslo]. Poslední příkaz (first (drop ... provede samotný výpočet sekvence prvočísel, zahodí prvních 999.999 hodnot a vrátí (pomocí first) tu 1.000.000tou. Vypočítat milion prvočísel chvilku trvá, takže třetí příkaz chvilku poběží. Spočítanou sekvenci už má pak ale Clojure nacachovanou, takže hodnoty pod milion nebo lehce nad vrací okamžitě.

17. července 2011

PragPub se věnuje Clojure

Vydavatelství The Pragmatic Bookshelf, které svého času založili někdejší signatáři Manifestu agilního vývoje softwaru Andy Hunt a Dave Thomas, vydává (zdarma) příjemný časopis, měsíčník PragPub. Aktuální červencové číslo se věnuje převážně Clojure. Jsou zde mmj. články o neměnnosti dat a kolekcích, nebo o DSL.

Kromě odkazovaného HTML vydání jsou na stránce vydaných čísel magazínu k dispozici i formáty mobi, epub a PDF.

15. června 2011

Rich Hickey: Radši trávím čas přemýšlením o problému

Skončil mi v Readeru zajímavý rozhovor s Richem Hickeym. Zajímavý byl i dotazovatel - Michael Fogus, autor knihy The Joy of  Clojure. Rozhovor je trochu delší a dost špatně (typograficky) formátovaný, ale kvůli tomu obsahu stojí za to se tím prokousat.

Rich hovoří o svých zkušenostech programátora, co bylo před Clojure, co Clojure pozitivně a negativně ovlivnilo, apod. Zmíněn je také Richův seznam na Amazonu Clojure Bookshelf (mmch. inspirativní seznam pro čtení, už před časem jsem si tam pár kousků vybral).

Mmj. mě zaujalo, že Rich je spíš na debuggování (debugging master) a nepíše testy (TDD):

"If people think that spending fifty percent of their time writing tests maximizes their results—okay for them. I’m sure that’s not true for me—I’d rather spend that time thinking about my problem. I’m certain that, for me, this produces better solutions, with fewer defects, than any other use of my time."

A co dělá Rich pro zlepšení svých programátorských skillů?

"I read relentlessly. I don’t do any programming not directed at making the computer do something useful, so I don’t do any exercises. I try to spend more time thinking about the problem than I do typing it in."

27. dubna 2011

Second mission

Navázal bych na post First Mission - napsal jsem další transformační skriptík pro stejný projekt. Opět šlo o to, přečíst vstupní soubor a na základě daných podmínek změnit některé hodnoty. Soubor obsahoval informaci o sdílených telefonech:

Tam, kde byla pro stejný telefon různá osobní čísla mělo dojít ke změně typu z PERSONAL na SHARED:
Měl jsem trochu obavy z "performance" (soubory mají 10k - 50k řádků), tak jsem to udělal na dva průchody - při prvním průchodu se do mapy vložily telefony, které měly více os. čísel a při druhém průchodu, kdy se všechny záznamy zapisovaly do upraveného souboru, se tyto záznamy měnily.

Skriptík je samozřejmě triviální, ale podstatné jsou věci, které jsem se přitom naučil - pořádně pracovat s Refs a transakcema (STM):
(def phones (ref {}))

(defn add-phone [phone pers-num]
  (if (contains? @phones phone)
      (if (contains? (@phones phone) pers-num)
          (println "Duplicate record:" phone pers-num)
          (dosync
            (ref-set phones (update-in @phones [phone]
                                       conj pers-num))))
      (dosync
        (ref-set phones (assoc @phones phone
                               #{pers-num})))))

(add-phone :123456789 :12)
(add-phone :123456789 :42)
(add-phone :123456789 :12)
(add-phone :123456790 :36)

(println @phones)
; Duplicate record: :123456789 :12
; {:123456790 #{:36}, :123456789 #{:12 :42}}
Další šikovná věc je "filrování mapy":
(defn get-shared [m]
  (select-keys m (for [[k v] m :when
                       (> (count v) 1)] k)))

(println
  (get-shared
    {:123456790 #{:36}, :123456789 #{:12 :42}})
; {:123456789 #{:12 :42}}
K tématu Refs bych se v budoucnu ještě rád vrátil - hned potom, co ze zásobníku vyndám a zpracuju sekvence.

12. února 2011

Uzávěr v laboratoři (jak začít)

Dneska bych tady měl dvě "drobnosti" pro ty, kdo chtějí s Clojure začít. Prvně to kratší: Chcete zkusit Clojure a přitom nic neinstalovat, jen si vyzkoušet jazyk? Na stránce Try Clojure je k dispozici online REPL, včetně krátkého tutoriálu. Jako perlička - k dispozici je Google Chrome rozšíření try-clojure-in-chrome, které zmíněnou stránku využívá pro zobrazení REPLu v Chrome.

[Updated: Zdá se, že posledních pár dní je stránka Try Clojure mrtvá. :-/ ]

Hlavní věc, o které bych ale dnes chtěl psát, je výborný Clojure tutoriál labrepl. Tutoriál vytvořil Stuart Halloway, autor vůbec první knihy o Clojure Programming Clojure a také jeden z hlavních contributorů do Clojure samotného.

Musím říct, že labrepl mě vyloženě nadchnul a to jak obsahem, tak implementací. Celý tutoriál je totiž v Clojure jak napsaný (HTML je generovaný pomocí Clojure web frameworku Compojure), tak zbuildovaný (konrétně Leiningenem, o kterém bych chtěl něco napsat příště). Tutoriál se spustí jako REPL, přičemž v rámci něho na pozadí běží web server Jetty s HTML tutoriálem a v již spuštěném REPLu se dají zkoušet jednotlivé příklady. Tento neohrabaný popis asi není moc názorný, takže radši doporučuji přímo vyzkoušet.

Nebudu tady popisovat obsah tutoriálu, jen bych zmínil věc, na kterou jsem zatím online nenarazil. A to je dokumentace přímo v REPLu. To je další vlastnost, která mne nadchla - zatím jsem se nesetkal s jazykem (s částečnou výjimkou v Pythonu), který by tak snadno a v takovém rozsahu něco podobného poskytoval.

Dokumentace funkce:
(doc zipmap)
; -------------------------
; clojure.core/zipmap
; ([keys vals])
;   Returns a map with the keys mapped to the corresponding vals.

Vyhledání dokumentace:
(find-doc "zip")
; -------------------------
; clojure.zip/seq-zip
; ([root])
;   Returns a zipper for nested sequences, given a root sequence
; -------------------------
; clojure.zip/vector-zip
; ([root])
;   Returns a zipper for nested vectors, given a root vector
; -------------------------
; clojure.zip/xml-zip
; ([root])
;   Returns a zipper for xml elements (as from xml/parse),
;   given a root element

Funkce javadoc otevře stránku Javadocu v prohlížeči (buď lokální, nebo vzdálenou):
(javadoc java.util.Date)
; "http://java.sun.com/javase/6/docs/api/java/util/Date.html"
; Created new window in existing browser session.

A nakonec funkce, která je nejvíc cool! source zobrazí implementaci dané funkce:
(source drop)
; (defn drop
;   "Returns a lazy sequence of all but the first n items in coll."
;   {:added "1.0"}
;   [n coll]
;   (let [step (fn [n coll]
;                (let [s (seq coll)]
;                  (if (and (pos? n) s)
;                    (recur (dec n) (rest s))
;                    s)))]
;     (lazy-seq (step n coll))))

9. února 2011

Sní androidi o Clojure?

Jsem asi poslední javista, který ještě nemá telefon s Androidem. Samozřejmě, že si ho jednou pořídím - je mi zkrátka sympatičtější než iOS nebo Symbian. Billův paskvil radši ani nebudu zmiňovat jménem :-) Každopádně mě napadlo, jak je na tom Android s Clojure (nebo naopak?).

Dobrá zpráva je, že Clojure Core počítá s podporou Androidu. Doslova je uvedeno "Clojure should run well there.". Horší je, že start aplikace zatím není příliš svižný (sluggish). Taky velikost aplikace je řádově rozdílná: Java 20 KB, Clojure 4 MB :-(

Nicméně jsem optimista - až si toho Androida jednou koupím, Clojure na něm bude svištět jako vítr :-)