5. dubna 2017

Kniha Living Clojure

Jsem beznadějný čtenář knih. Musím denně číst a ačkoliv jsou dnes k dispozici tuny textů v různých formách, mě nejvíc vyhovují knihy.

Jedinou úlitbou modernosti je, že pro odbornou literaturu mi více vyhovují ty digitální. Přece jenom - do Kindlu se toho vejde kupa a zastaralé vydání vám nepřekáží v reálném světě.

Back to the Future

Ani nevím, co mě to cvrnklo přes nos, ale po pětileté přestávce se vracím ke Clojure. Pět let je v softwarovém inženýrství dlouhá doba. Technologie můžou zaniknout, či se nečekaně rozvinout, nebo nabrat úplně jiný směr. A těší mě, že Clojure stále rozkvétá.

Říkal jsem si - jak se do toho zase dostat? No dobře, je to řečnická otázka. Spíš jsem si říkal, kterou knihu si vybrat? Když jsem s Clojure začínal, byla na trhu jediná kniha - Programming Clojure od @StuartHalloway. Byla to tehdy bible Clojure. A možná ještě je - letos vyjde třetí edice!

Udělal jsem si seznam a jako první volbu jsem hodně zvažoval oceňované Joy of Clojure, jejíž první edici jsem kdysi nedočetl. Nakonec ale převážila náhoda. (Mimochodem, kdyby vás ten seznam zajímal, najdete ho u mne na Goodreads. Nebo ve webové verzi zaskrolujte po pravé straně dolů.)

Gigasquid!

Že jsem zase vlítnul do Clojure je více méně náhoda. Nebo osud. Minulý podzim jsem se byl po čase podívat na Java konferenci - český GeeCON - a tam jsem potkal Carin Meier, aka @Gigasquid. (Už jsem o tom psal na svém druhém blogu - GeeCON Prague 2016, den 2.)

Carin stojí za to sledovat. Kromě výše a níže zmíněné knihy, pracuje v Cognitectu. Ano, v té firmě, kde se koncentruje Clojure smetánka (posuďte sami), včetně polo-boha Riche Hickeyho.

Pokud budete mít možnost, určitě si s Carin popovídejte. Zvládl jsem to i já, zavilý introvert. Zakecal jsem se s ní tak moc, až jsem v podstatě prošvihnul následnou přednášku.

Alice in Wanderland

No dobře, to bylo keců... co ta kniha? Je dobrá, dal jsem jí na Goodreads 5 hvězd. Samozřejmě, v daném kontextu - je to dobrá kniha pro začátečníky, nebo pro někoho, kdo si potřebuje po čase Clojure oživit.

Kniha prochází všechny základní témata, která byste v učebnici čekali a skládá je v rozumném pořadí:
  • Kolekce a sekvence
  • Funkcionální programování
  • Stav a konkurence
  • Java interoperabilita
  • Leiningen a Clojure projekt
  • Asynchronni komunikace s core.async
  • Clojure web applikace
  • Makra
Napsáno pěkně, stručně, přehledně. Kromě toho, ale přináší druhá část knihy tréninkový program: Clojure v 7 týdnech.

Jak píše Carin v úvodu, učení se nového jazyka má silné paralely, jako když chcete zčista jasna začít běhat: důležitá je motivace, pravidelnost a... nepřetrénovat se.

Ohledně běhání, na webu je dnes spousta tréninkových plánů od 5 kilometrů až po maraton. Carin k tomu přistupuje obdobně - začít zvolna a postupně navyšovat objemy a obtížnost.

Prvních pár týdnů jde o řešení problémů na 4clojure. Pak se přesuneme na GitHub, kde má Carin několik kata, inspirovaných příběhem Alenky v říši divů: wonderland-clojure-katas. Poslední týden je pak věnovaný deploymentu Clojure aplikace na Heroku.

A je to! Kniha je přečtená a vy jste fluent v Clojure :-)  No, tak jednoduché to není. Ale je to dobrý začátek. Jeden z možných.

25. března 2017

Clojure concurrency: Vars

Významnou vlastností Clojure jsou neměnitelné datové struktury. Je to taková dvojsečná vlastnost (i když benefity výrazně převažují). Na jednu stranu to vývoj zjednodušuje, protože se nemusíme bát, že se nám data změní pod rukama.

Nadruhou stranu vyvstává otázka, jak s neměnitelnými daty pracovat - nic v našem světě není neměnné (a všechno jednou pomine). Jak tedy Clojure řeší změnu stavu a s tím související konkurenci?

K dispozici jsou čtyři mechanizmy, jak měnit stav:
  • Vars jako globální úložiště, pro eventuální per-thread změny.
  • Atoms pro synchronní, nekoordinované změny.
  • Refs pro synchronní, v transakci koordinované změny.
  • Agents pro asynchronní, nekoordinované změny.
Postupně bych se chtěl podívat na všechny způsoby, ale dnes začneme tím nejjednodušším, čemu se žádný začátečník v Clojure nevyhne.

Vars

Var je způsob, jakým Clojure odkazuje na úložiště. Toto úložiště obsahuje "standardní", neměnná data. Var, ale můžeme dočasně přesměrovat na jiné úložiště. Podstatnou vlastností je, že toto dočasné přesměrování - binding - je vidět jenom v rámci aktuálního vlákna, ostatní vlákna vidí pořád původní hodnotu.

Statické Vars

Klasické Var se vytvoří speciálním formem def: (def x 42). Takto definováno, je Var statické - pokud bychom ho chtěli svázat s jiným úložištěm, vyhodí Clojure výjimku IllegalStateException.


Dynamické Vars

Pokud chceme, aby Var ukazovalo na jiné úložiště, musíme ho explicitně definovat jako dynamické pomocí instrukce ^:dynamic. Samotné přesměrování se provádí makrem binding. Po dobu trvání bloku binding směřuje Var na nové úložiště, aby se po jeho skončení vrátilo ke své původní hodnotě.

Dle konvence se dynamické Vars uzavírají do *earmuffs*. Označují se tak věci, určené pro re-binding.


Vars jsou globální

Vars jsou globální - viditelné pro všechny thready. Pokud tedy vytvoříme Var v jiném vlákně, je dosažitelné také ze všech ostatních vláken.

V následujícím příkladu vytvoříme Var v jiném vlákně pomocí makra future:


Namespace

V rámci namespace, v němž byly vytvořeny, jsou Vars přístupné svým názvem. Pokud se přepneme do jiného namespacu, je potřeba na Var odkazovat plně kvalifikovaným názvem, nebo ho "naimportovat" z původního namespace pomocí funkce refer.


Funkce jsou také Vars

Ve Vars se neukládají jenom data, ale také funkce. Všechno výše napsané tedy platí pro funkce úplně stejně, včetně toho, že je můžeme dynamicky re-bindovat.

To může být zajímavý mechanismus, jak v runtimu dynamicky měnit chování funkcí. Nicméně, užívejte s mírou - jazyk to umožňuje, ale je to výjmečné řešení.


A kde je ta konkurence?

Se samotnými Vars si moc konkurentního programování neužijeme. Je to ale základní stavební kámen, od kterého se odvíjejí ostatní způsoby. Konec konců, všechny v úvodu zmíněné mechanizmy - Atomy, Refs a Agenti - jsou ve výsledku uloženy ve Vars.

Související články

  • TBD: Clojure concurrency: Atoms
  • TBD: Clojure concurrency: Refs
  • TBD: Clojure concurrency: Agents

Starší související články

18. března 2017

Catalanova čísla a syntax highlighting

Je to téměř 5 let, co jsem naposled napsal něco na tento blog. Ale doufám, že se to teď změní - moje láska ke Clojure opět propukla s neztenčenou silou a tak snad ponese nějaké užitečné ovoce.

Chvilku ale potrvá, než uvedu blog do použitelného stavu, takže začnu takovým zahřívacím tématem. Tedy, dvěma tématy - sice to není úplně fér, ale vrazil jsem do titulku dva nesouvisející motivy.

Syntax highlighting 3.0

Když jsem s Clojure před šesti lety začínal, bylo ve verzi 1.2 a jeho podpora v různých nástrojích byla nízká, nebo žádná. Jednu z věcí, které jsem tehdy řešil, bylo jak zobrazit na blogu syntax highlighting.

Moje první volba padla na tehdy všudypřítomný SyntaxHighlighter. Musel jsem použít jakýsi externí Clojure brush, protože SyntaxHighlighter dodnes nemá nativní podporu Clojure.

S výsledkem jsem nebyl spokojen, takže jsem po čase zvolil nové řešení - jako letitý uživatel famózního editoru Vim, jsem se rozhodl pro trochu ruční práce a zaangažoval vimovskou funkci :TOhtml. S tou jsem maximálně spokojen a používám ji i na svém druhém blogu SoftWare Samuraj. Přesto jsem se rozhodl potřetí pro změnu syntax highlightingu.

Ten třetí důvod má konsekvence s oživením blogu. Aniž bych si tady vylíval srdíčko, jeden z důsledků mých aktuálních rozhodnutí je, vybudovat si na svém GitHubu jakési Clojure portfolio (už jsem o tom kdysi psal).

Volba GitHubu pro mne nebyla úplně přímočará - nejsem "git guy" a pokud můžu, preferuji Mercurial. Pravdou ale je, že veškerá Clojure komunita je GitHubu, tak proč se tomu vzpírat. No, a když GitHub, tak Gist, to dá rozum.

Catalanova čísla

V rámci svého Clojure-zmrtvýchvstání jsem procházel své staré Clojure kódy a narazil jsem na jeden, který se mi líbil - funkce pro výpočet Catalanových čísel. Pikantní je, že si po těch pěti letech nepamatuju, proč jsem to napsal, nicméně funkce se mi líbily natolik, že jsem se rozhodl se o ně - v rámci zahřátí na provozní teplotu - podělit.

Catalanova čísla jsou sekvencí přirozených čísel, která má zajímavé využití v kombinatorice - binární stromy, průchod mřížkou(?), rozdělení polygonu na trojúhelníky ad. Sekvence je definována následujícím vztahem:


Pokud, tak jako já, nejste úplně kovaní v matematice, tak chviličku googlování potrvá, než zjistíte, že pro implementaci tohoto vzorečku budete potřebovat kombinační číslo (binomial coefficient) a tím pádem faktoriál. Pak už je jednoduché poskládat tyto základní funkce do sebe, jako matrojšku:


Když už jsem se v tom tak vrtal, říkal jsem si: nedá se to udělat nějak jednodušeji, efektněji, víc "Clojure"? A dá. Jen je potřeba vyjít z jiného vzorečku, který definuje Catalanova čísla rekurzivně:
Při použití posledního vzorečku nám tak vyjde pěkná Clojure rekurze. Klíčová slova jsou speciální operátory loop a recur:


GitHub project

Pokud si chcete pohrát s Catalanovými čísly trochu víc, anebo dostat malý bonus navíc, podívejte se na můj projekt na GitHubu:
Navíc dostanete Leiningen projekt, testy v Midje a lazy sekvenci Catalanových čísel.

Související články

5. května 2012

Map a reduce, funkcionální elegance

Na svém druhém blogu o softwarovém inženýrství jsem se pustil do kratičké sérieHadoopu - Java implementaci MapReduce. Protože tento algoritmus je inspirován dvěma funkcemi pocházejícími z funkcionálního programování - map a reduce - podíval bych se trochu blíže, jak tyto funkce vypadají v Clojure.

Map

Začněme výpisem dokumentace a odkazem na referenci:

Dokumentace funkce map
Jak je vidět, funkce map přijímá v nejjednodušší podobě dva parametry - funkci a kolekci. Na každou položku kolekce pak aplikuje danou funkci.
(map inc '(1 2 3))
;=> (2 3 4)
(map #(* % %) '(1 2 3))
;=> (1 4 9)
(map #(.toUpperCase %) '("a" "b" "c"))
;=> ("A" "B" "C")
Pokud v argumentech po funkci následuje více kolekcí, je funkce postupně aplikována na všechny první položky, druhé položky atd., dokud v jedné z kolekcí nedojde počet položek.
(map str '(1 2 3 4) '("a" "b" "c"))
;=> ("1a" "2b" "3c")

; takes the first column
(map first [[:a1 :a2 :a3]
            [:b1 :b2 :b3]
            [:c1 :c2 :c3]])
;=> (:a1 :b1 :c1)

; removes the first column
(map rest [[:a1 :a2 :a3]
           [:b1 :b2 :b3]
           [:c1 :c2 :c3]])
;=> ((:a2 :a3)
;    (:b2 :b3)
;    (:c2 :c3))

Reduce

Jestliže u funkce map vkládáme do funkce kolekci a výsledkem je opět (nějak zpracovaná) kolekce, u funkce reduce sice také vkládáme do funkce kolekci, ale výsledkem je jedna hodnota, objekt, entita. Algoritmus je následující: funkce reduce vezme úvodní hodotu a první hodnotu z kolekce a aplikuje na ně danou funkci, pak vezme výsledek a druhou hodnotu z kolekce a aplikuje na ně danou funkci. Takto iteruje až do konce kolekce.

Dokumentace funkce reduce
Pokud funkce neobsahuje úvodní hodnotu, je první aplikace funkce na první dvě hodnoty kolekce. Zbytek už je stejný.
(reduce * '(3 4 5))
;=> 60
(reduce * 2 '(3 4 5))
;=> 120
(reduce + (range 1 1000001))
;=> 500000500000
(reduce max '(7 4 -3 42 -6 12))
;=> 42
(reduce conj [1 2] [3 4 5])
;=> [1 2 3 4 5]

Příklad

Pojďme si teď napsat praktický příklad, na kterém si obě funkce vyzkoušíme a to i s postupným výkladem, jak jsme se k jejich potřebě dopracovali. Dejme tomu, že chci mít funkci, která mi vrací SHA-1 hash daného řetěze. Něco jako:
(sha1 "Sometimes Clojure")
;=> "339e36ff40c581cebf3bec542c9a699a4ffbb453"
Pro výpočet hashe (digestu) použijeme Java třídu MessageDigest. Naše funkce by mohla vypadat nějak takhle:
(import java.security.MessageDigest)

(defn digest [string]
  (.digest (MessageDigest/getInstance "SHA-1")
           (.getBytes string)))

(digest "Sometimes Clojure")
;=> #<byte[] [B@36d6526d>
Metoda/funkce digest (ta naše i ta Javovská) vrací pole bytů (binární řetězec). Můžeme si je prohlédnou např. příkazem:
(reduce conj [] (digest "Sometimes Clojure"))
;=> [51 -98 54 -1 64 -59 -127 -50 -65 59
;   -20 84 44 -102 105 -102 79 -5 -76 83]
(apply vector (digest "Sometimes Clojure"))
;=> [51 -98 54 -1 64 -59 -127 -50 -65 59
;   -20 84 44 -102 105 -102 79 -5 -76 83]
Pokud bychom pole bytů převedli na řetězec, nebude to asi to, co bychom čekali:
(String. (digest "Sometimes Clojure"))
;=> "3�6�@Łο;�T,�i�O�"
Napíšeme si proto pomocnou funkci, která převede byte na hexadecimální hodnotu:
(defn hex [bt]
  (if (< (bit-and 0xff bt) 0x10)
    (str 0 (Integer/toHexString (bit-and 0xff bt)))
    (Integer/toHexString (bit-and 0xff bt))))
Nyní již můžeme použít funkci map, abychom získali hash v podobě hexadecimálního pole:
(map hex (digest "Sometimes Clojure"))
;=> ("33" "9e" "36" "ff" "40" "c5" "81" "ce" "bf" "3b"
;    "ec" "54" "2c" "9a" "69" "9a" "4f" "fb" "b4" "53")
No a samozřejmě, že cílem je, mít hash jako řetězec. K tomu nám dopomůže právě funkce reduce:
(reduce str (map hex (digest "Sometimes Clojure")))
;=> "339e36ff40c581cebf3bec542c9a699a4ffbb453"
Celý kód pak vypadá takto:
(ns blog-map-reduce.core
  (:import java.security.MessageDigest))

(defn digest [string]
  "Returns a SHA-1 digest of the given string."
  (.digest (MessageDigest/getInstance "SHA-1")
           (.getBytes string)))

(defn hex [bt]
  "Returns a hexadecimal value of the given byte."
  (if (< (bit-and 0xff bt) 0x10)
    (str 0 (Integer/toHexString (bit-and 0xff bt)))
    (Integer/toHexString (bit-and 0xff bt))))

(defn sha1 [string]
  "Returns a SHA-1 hash of the given string."
  (reduce str (map hex (digest string))))
S funkcemi si pak můžeme hrát ještě dál. Můžeme např. mít pole řetězců a budeme chtít vrátit pole hashů:
(map sha1 '("Sometimes Clojure" "Guido"))
;=> ("339e36ff40c581cebf3bec542c9a699a4ffbb453"
;    "28aeafc90fae0f3318d4bdf5b1a59e5dc506b50a")
A nebo můžeme jít ještě dál a chtít jako výsledek mapu, kde hash bude klíčem k původnímu řetezci. Hash-klíče navíc převedeme, kvůli performance, na Clojure keywords:
(defn sha1-map [strings]
  "Returns a map of SHA-1 hashes as a keyword keys
   and given string as a value."
  (zipmap (map keyword (map sha1 strings)) strings))

(sha1-map '("Sometimes Clojure" "Guido"))
;=> {:28aeafc90fae0f3318d4bdf5b1a59e5dc506b50a
;        "Guido",
;    :339e36ff40c581cebf3bec542c9a699a4ffbb453
;        "Sometimes Clojure"}

Závěr

V nadpisu jsem zmiňoval (funkcionální) eleganci. Pro vyznavače Clojure a Lispu je to samozřejmě nošením sov do Athén. Nicméně i člověk nezasažený Lispovskou syntaxí a funkcionálním programováním by mohl uznat, že uvedené ukázky jsou elegantní a pokud si za Lisp/Clojure dosadíme pseudo-kód, tak i čitelné a pochopitelné.

Jen pro představu, jak by vypadal výše uvedený kód v jiném jazyce, jsem přepsal funkci sha1-map do Groovy. Při srovnání bych řekl, že kód v Groovy je "ukecanější" a méně flexibilnější než ten v Clojure. To samozřejmě není nic proti tomu, pokud bychom to srovnávali s Javou - Groovy má alespoň metodu each, která přijímá closure.
import java.security.MessageDigest

def digest(string) {
    def md = MessageDigest.getInstance("SHA-1")
    md.digest(string.getBytes())
}

def hex(bt) {
    if ((0xff & bt) < 0x10) {
        "0" + Integer.toHexString(0xff & bt)
    } else {
        Integer.toHexString(0xff & bt)
    }
}

def sha1(string) {
    def result = new StringBuilder()

    digest(string).each {
        result.append(hex(it))
    }

    result
}

def sha1Map(strings) {
    def result = new HashMap()

    strings.each {
        result.put(sha1(it), it)
    }

    result
}

sha1Map(["Sometimes Groovy", "Guido"])
// [28aeafc90fae0f3318d4bdf5b1a59e5dc506b50a :
//      Guido,
//  ac0416ec9a83cd93a5f5a2aa5e6a452cd33840db :
//      Sometimes Groovy]

12. dubna 2012

Leiningen, jak nemít vlasy v ohni

Leiningen je buildovací a projektový nástroj pro Clojure, který se velmi silně inspiroval Javovským Mavenem nebo Groovyovským Gradle. Jeho podtitulem je "automating Clojure projects without setting your hair on fire". Po vlastních zkušenostech musím říct, že podtitul je zvolen velmi trefně. Leiningen bych rozhodně doporučil, jako startovací bod pro seznamování se s Clojure.

Pravdou je, že prvotní rozchození Clojure je tvrdým oříškem i pro zkušené hackery a pro nováčky může být trochu stresující. (OK, tak pro ty z nás, kteří jsou opravdu dobří, to zas takový problém není ;-)

Instalace

Instalace na linuxu je triviální (viz README):
  1. stáhnne se skript,
  2. přidá se do $PATH,
  3. udělá se spustitelným (chmod 755 lein).
Na Windows je to o něco málo složitejší (opět, viz README).

Korektní instalaci ověříme příkazem lein version:
Leiningen 1.7.1 on Java 1.7.0_147-icedtea OpenJDK 64-Bit Server VM

REPL

REPL bývá silnou zbraní skriptovacích jazyků. Nejinak je tomu i v Clojure. Pokud máme nainstalovaný Leiningen, můžeme rovnou začít používat Clojure (bez jeho instalace) příkazem:
lein repl

REPL ukončíme buď klávesovou zkratkou Ctrl-C, nebo příkazem:
(System/exit 0)

První projekt

Leiningen umožňuje vytvořit kostru nového projektu pomocí příkazu:
lein new <project>

Vytvoření a layout projektu 

V layoutu projektu vidíme klasické rozdělení na zdrojový/produkční kód a testy (adresáře src a test) a také soubor s definicí projektu project.clj.

Pokud chceme mít v projektu jiné namespacy, než je název projektu můžeme použít pro vytvoření projektu syntaxi:
lein new project directory

Projekt s jiným namespacem

Definice projektu

Konfiguračním souborem pro projekt (a Leiningen) je project.clj, jehož iniciální podoba je na následujícím výpisu:
(defproject my-project "1.0.0-SNAPSHOT"
  :description "FIXME: write description"
  :dependencies [[org.clojure/clojure "1.3.0"]])
Nejzajímavější položkou je keyword :dependencies, které (překvapivě) definuje závislosti projektu. Podobně jaku v Mavenu jsou závislosti stahovány z repository - defaultními úložišti jsou Clojars a Maven Central. Další repository lze přidat pomocí keywordu :repositories.
:repositories {
  "my-local" "http://my-local/repository"
  "java.net" "http://download.java.net/maven/2"}
Pokud chceme použít některé závislosti pouze při vývoji, použijeme pro jejich definici keyword :dev-depencencies. Já ho používám např. pro Midje.
:dev-dependencies [[midje "1.3.2-SNAPSHOT"]
                   [lein-midje "1.0.8"]]
Pokud budeme výsledný projekt distribuovat jako spustitelný JAR soubor, hodí se nám keyword :main, které definuje namespace, ve kterém se nachází funkce -main (která bude defaultně zavolána při spuštění lein run (viz dále)); a také se zpropaguje do položky Main-Class v MANIFEST.MF.

Když už jsme u manifestu, existuje i keyword :manifest, které umožní vložit specifické položky do souboru MANIFEST.MF.
:main blog-lein.core
:manifest {"Build-Version" ~project-version
           "Built-Time" ~(str (Date.))}

Další tasky

Kompletní seznam tasků Leiningenu lze vypsat příkazem:
lein help

Ty které jsou shodné s Mavenem není potřeba představovat/vysvětlovat:
  • clean
  • compile
  • deploy
  • install
  • test
Z těch ostatních jsou zajímavý:
  • classpath - vypíše classpath aktuálního projektu.
  • deps - dotáhne z repozitářů závislosti definované v :dependencies a :dev-dependencies. Defaultní umístění je adresář lib, ev. lib/dev a dá se ovlivnit keywordem :library-path.
  • pom - vytvoří z project.clj soubor pom.xml pro Maven.
  • repl - spustí REPL, buď samostatně (standalone), nebo v rámci projektu - v tom případě přidá do classpath všechny knihovny definované v :dependencies.
  • run - spustí -main funkci projektu. Namespace funkce musí být definován keywordem :main.
  • search - prohledá repozitáře a vypíše dostupné závislosti.
  • uberjar - zabalí všechny soubory a závislosti do jednoho JAR souboru. Vhodné pro standalone distribuci. Pokud má být JAR spustitelný, musí namespace s funkcí -main obsahovat ve své deklaraci direktivu :gen-class.

Zkušební projekt

Pro studijní důvody jsem vytvořil jednoduchý projekt, na kterém se dají vyzkoušet výše uvedené příkazy (samozřejmě kromě lein new):
[Update]Projekt je k dispozici na Bitbucketu: blog-lein[/Update]

Projekt má následující definici (soubor project.clj):
(import java.util.Date)
(def project-version "1.0.0-SNAPSHOT")
(defproject blog-lein project-version
  :description "Sometimes Clojure, Leiningen"
  :dependencies [[org.clojure/clojure "1.3.0"]]
  :dev-dependencies [[midje "1.3.2-SNAPSHOT"]
                     [lein-midje "1.0.9"]]
  :main blog-lein.core
  :manifest {"Build-Version" ~project-version
             "Built-Time" ~(str (Date.))})
Doporučoval bych vyzkoušet si:
  • lein classpath
  • lein jar (a mrknout do MANIFEST.MF)
  • lein pom
  • lein repl (a zkusit si nějaké Midje funkce)
  • lein run
  • lein uberjar (a spustit si kód pomocí java -jar <jarfile>.jar)
Věřím, že po tomto malém úvodu se vám bude s Clojure začínat mnohem jednoduššeji (a nemít přitom vlasy v jednom ohni ;-)

2. dubna 2012

Lepší testování v Clojure. Midje

Dneska navážu na unit testování v Clojure, nicméně dostanu se k tomu oklikou. Minulé léto se celkem hojně psalo o desátém výročí deklarace Manifesto for Agile Software Development. Internetem (a mojí čtečkou) tehdy protekly nějaké rozhovory s někdejšími účastníky/signatáři. Jedním z nich byl i Brian Marick, autor Midje, testovacího frameworku pro Clojure (ano, to je to Midje, které jsem zmiňoval ve svém postu o ThoughtWorks Radar).

Instalace/zprovoznění

Midje je nejjednodušší začít používat pomocí Leiningenu (určitě jeden z příštích zápisků). Definice projektu může vypadat třeba takto:
(defproject blog-midje "1.0.0-SNAPSHOT"
            :description "Midje for Sometimes Clojure"
            :dependencies [[org.clojure/clojure "1.3.0"]]
            :dev-dependencies [[midje "1.3.2-SNAPSHOT"]])
REPL se pak spustí příkazem lein repl.

Fakta

Základem testování v Midje jsou fakta. Fakta o budoucím kódu (test first):
(ns blog-midje (:use midje.sweet))
; nil
(fact (+ 1 1) => 2)
; true
(fact 1 => odd?)
; true
(fact "truth about one" 1 => even?)
; FAIL at (NO_SOURCE_FILE:11)
; Actual result did not agree with the checking function.
;         Actual result: 1
;     Checking function: even?
; false
Fakt může mít více klauzulí. Dá se použít makro fact nebo facts:
(facts "about42"
       (+ 21 21) => 42
       (* 6 7) => 42)
; true
Pokud je potřeba fakt prezentovat pomocí sady hodnot, nabízí Midje tzv. tabulková fakta:
(defn xor [x y]
  (if (= x y) false true))
; #'blog-midje/xor

(tabular
  (fact "The rules of XOR"
        (xor ?x ?y) => ?expected)
  ?x    ?y    ?expected
  0     0     false
  0     1     true
  1     0     true
  1     1     false
  false false false
  false true  true
  true  false true
  true  true  false)
; true
Vyhození výjimky se dá otestovat pomocí funkce (checkeru) throws:
(fact (/ 42 0) => (throws ArithmeticException))
; true

Spuštění testů

Midje předpokládá pro spuštění (všech) testů nějaký nástroj, jako např. Leiningen nebo Cake. Pojďme se podívat, jak by vypadalo rozchození projektu a testů pomocí Leiningenu. Nový projekt vytvoříme příkazem:
lein new blog-midje

Struktura projektu by měla vypadat takto:

Layout projektu

Do definice projektu (project.clj) je potřeba přidat závislosti. Kromě těch obligátních je to hlavně knihovna lein-midje:
(defproject blog-midje "1.0.0-SNAPSHOT"
            :description "Midje for Sometimes Clojure"
            :dependencies [[org.clojure/clojure "1.3.0"]]
            :dev-dependencies [[midje "1.3.2-SNAPSHOT"]
                               [lein-midje "1.0.8"]])
Prvně napíšeme test (soubor test/blog_midje/test/core.clj):
(ns blog-midje.test.core
  (:use [blog-midje.core])
  (:use [midje.sweet]))

(tabular
  (fact "The rules of XOR"
        (xor ?x ?y) => ?expected)
  ?x    ?y    ?expected
  0     0     false
  0     1     true
  1     0     true
  1     1     false
  false false false
  false true  true
  true  false true
  true  true  false)
Potom napíšeme testovanou funkci (src/blog_midje/core.clj):
(ns blog-midje.core)

(defn xor
    "Returns true if x and y are mutually exclusive."
    [x y]
    (if (= x y) false true))
Testy potom spustíme příkazem lein test, nebo lépe lein midje:

Spuštění testů

Celý projekt je ke stažení zde: blog-midje.zip.

[Update]Celý projekt je dispozici na Bitbucketu: blog-midje[/Update]

To je pro dnešek vše. Pokud se chcete podívat na úvod do Midje od samotného Briany Maricka, zkuste  videa uvedená na wiki stránce Midje.


31. března 2012

Změna syntax highlightingu a konvence kódu


Změna syntax highlightingu
Když jsem začínal psát tenhle blog, řešil jsem, jak prezentovat ukázky kódu, tedy syntax highlighting. Tehdy jsem zvolil řešení, které jsem dost často vídal na jiných blozích - SyntaxHighlighter. Řešení je to sice pěkné, ale bohužel pro Clojure neobsahuje oficiální brush a ten který byl k dispozici neoficiálně, nepokrýval plně syntaxi Clojure.

Shodou okolností, tento rok mám takové malé jubileum - už deset let jsem uživatelem Linuxu a protože hned na počátku jsem řešil klasické linuxové dilema, jestli programovat v Vi(m)u nebo v Emacsu a tehdy jsem zvolil Vim (a dodones nelituju); můžu zároveň říct, že jsem i desetiletý uživatel Vimu.

No, zpátky k syntaxi.Vim obsahuje funkci na převod kódu do HTML:
  • v textové verzi se zadá příkaz :TOhtml,
  • v GUI verzi je to v menu Syntax -> Convert to HTML.
Pro úplnost bych ještě doplnil, že Vim neumí syntaxi Clojure sám od sebe, ale je potřeba "doinstalovat" plugin VimClojure.
Změna code convention
Když už jsem řešil změnu syntax highlightingu (protože jsem to musel ručně poměnit u všech publikovaných postů), rozhodl jsem se změnit i code convention - to, jak budu zapisovat ukázky kódu. Sympatická mi přijde konvence použitá v knize The Joy of Clojure, která spočívá v tom, že se:
  • píše čistý Clojure kód,
  • nepíše se výzva REPLu, nebo příkazového řádku a
  • výpisy na standard output se vypisují za příkaz jako komentáře.
Výhodou téhle konvence je, že kód lze rovnou bez úprav zkopírovat a spustit ho v REPLu nebo vložit do skriptu a zároveň je vidět, co kód dělá (jeho výstup).

Takže místo:
user=> (def my-ref (ref "immutable data"))
#'user/my-ref
user=> my-ref
#<Ref@467d8ddd: "immutable data">
user=> @my-ref
"immutable data"
user=> (deref my-ref)
"immutable data"

vypadá kód takto:
(def my-ref (ref "immutable data"))
; #'user/my-ref
my-ref
; #<ref@18352d8: "immutable data">
@my-ref
; "immutable data"
(deref my-ref)
; "immutable data"
To je celkem přehledný, ne?