Programovací jazyk Pixie: funkce ze základní knihovny a použití FFI

25. 10. 2016
Doba čtení: 25 minut

Sdílet

Ve druhé a současně i závěrečné části článku o jazyce Pixie si nejprve ukážeme nejdůležitější funkce ze základní knihovny používané v prakticky všech aplikacích a posléze se budeme zabývat problematikou FFI.

Obsah

1. Programovací jazyk Pixie: funkce ze základní knihovny a použití FFI

2. Použití klasické rekurze

3. Použití tail rekurze

4. Opakování části kódu s použitím konstrukce dotimes

5. Průchod prvky sekvence s využitím doseq

6. Generátorová notace seznamu – list comprehension

7. Praktický příklad – výpočet Eulerova čísla s využitím doseq, forreduce

8. Použití programové konstrukce loop/recur

9. Použití FFI aneb volání nativních funkcí

10. Nativní funkce dostupné ze standardních modulů jazyka Pixie

11. Příklad – vygenerování perexového obrázku k tomuto článku

12. Problém s nativními funkcemi typu printf

13. Odkazy na předchozí části tohoto seriálu

14. Odkazy na Internetu

1. Programovací jazyk Pixie: funkce ze základní knihovny a použití FFI

V úvodním článku o programovacím jazyku Pixie jsme se seznámili se základními vlastnostmi tohoto programovacího jazyka. Připomeňme si, že Pixie se po stránce syntaxe a z velké části i sémantiky podobá jazyku Clojure, ovšem s tím rozdílem, že zatímco Clojure je určen pro běh nad nějakým větším a univerzálnějším virtuálním strojem (JVM, CLR, VM JavaScriptu), má programovací jazyk Pixie vlastní (výrazně menší) virtuální stroj využívající PyPy. Dnes se nejprve seznámíme s dalšími funkcemi a makry ze základní knihovny, které jsou určeny pro implementaci programových smyček, tail rekurze či generátorové notace seznamu (list comprehension). Ve druhé části se budeme zabývat problematikou FFI, tj. volání nativních funkcí přímo ze skriptů napsaných v jazyce Pixie.

2. Použití klasické rekurze

V programovacím jazyku Pixie se, podobně jako v mnoha dalších programovacích jazycích, většinou nepoužívají klasické programové smyčky. Namísto nich se využívají funkce vyššího řádu (map, reduce, filter atd.), generátorová notace seznamu představovaná makrem for, klasická přímá rekurze nebo tzv. tail rekurze. Nejprve si ukažme použití přímé rekurze, tj. takového zápisu, kdy se v těle vytvářené funkce může objevit volání této funkce. Zcela typickým „školním“ příkladem rekurzivní funkce je funkce pro výpočet faktoriálu, jejíž jednoduchá varianta (neochráněná před všemi typy vstupů) může vypadat takto:

user=> (defn fact [n] (if (<= n 1) 1 (* n (fact (- n 1)))))
#'user/fact

Funkci lze samozřejmě přepsat do čitelnějšího tvaru:

(defn fact
    [n]
    (if (<= n 1)
        1
        (* n (fact (- n 1)))))

Podobným způsobem je možné rekurzivně implementovat funkci pro výpočet největšího společného dělitele dvou přirozených čísel. V následující implementaci schválně nepoužívám výpočet zbytku po dělení, ale pouze postupný výpočet rozdílu mezivýsledků:

(defn gcd
    [x y]
    (if (= x y) x
                (if (> x y)
                    (gcd (- x y) y)
                    (gcd x (- y x)))))

Opět si zkusme tuto funkci otestovat. Pro malé vstupní hodnoty (přirozená čísla!) pracuje bez problémů:

user => (gcd 1 1)
1
 
user => (gcd 1 2)
1
 
user => (gcd 4 2)
2
 
user => (gcd 6 9)
3
 
user => (gcd 12 8)
4
 
user => (gcd 1024 768)
256

Co se však stane pro větší vstupy?:

user => (gcd 123456 98765)
ERROR:
 RuntimeException: :pixie.stdlib/InternalError Internal error: <StackOverflow object at 0xccc6f8>

Vidíme, že zápis algoritmu přímou rekurzí sice může být elegantní a může vycházet přímo z definice řešení problému (viz faktoriál, ovšem i spousta dalších funkcí je v matematice definována rekurzivně), ale z praktického hlediska zde evidentně existují technická omezení.

3. Použití tail rekurze

Důvod, proč předchozí volání funkce gcd skončilo s chybou, spočívá v tom, že došlo k přeplnění zásobníku při rekurzivním volání. Na zásobník se totiž musí v takzvané winding fázi ukládat parametry předávané volané funkci a taktéž body návratu (zjednodušeně řečeno návratové adresy). Aby k přetečení zásobníku nedocházelo, můžeme naši funkci gcd upravit tak, aby se využívalo takzvané tail rekurze. Velmi zjednodušeně řečeno je tail rekurze použita tehdy, pokud je posledním příkazem nějaké funkce příkaz pro rekurzivní volání té samé funkce. V tomto případě se nemusí na zásobník nic ukládat a namísto toho se prostě provede skok. V jazyku Pixie se však musí tail rekurze zapsat explicitně, což má své přednosti i zápory (podle mě převažují přednosti, protože již ze zápisu programu je zcela zřejmé, kdy k tail rekurzi skutečně dojde).

Explicitní zápis rekurze spočívá ve využití speciální formy recur, která se zapíše přesně do místa, kde má k tail rekurzi (=skoku) dojít. U funkce gcd je použití přímočaré, prostě se na posledních dvou řádcích nahradí (gcd parametry) za (recur parametry):

(defn gcd
    [x y]
    (if (= x y) x
                (if (> x y)
                    (recur (- x y) y)
                    (recur x (- y x)))))

Chování si můžeme ihned otestovat na příkladu, který v případě zápisu algoritmu přímou rekurzí zhavaroval:

user => (gcd 123456 98765)
1

Ještě větší hodnoty také nezpůsobí žádné problémy:

user => (gcd 120000000000 800224)
32

Mohlo by se zdát, že by překladač programovacího jazyka Pixie snad bylo možné zmást a použít recur jako náhradu skoku kdekoli. Ve skutečnosti si však překladač hlídá, zda je recur použito správně:

(defn fact
    [n]
    (if (<= n 1)
        1
        (* n (recur (- n 1)))))

Překladač vypíše následující (zcela korektní) chybové hlášení:

ERROR:
 in pixie function repl_fn
 
in pixie/repl.pxi at 27:24
               (let [x (eval form)]
                       ^
in internal function eval
 
RuntimeException: :pixie.stdlib/AssertionException Can't recur in non-tail position

Funkci fact je totiž nutné nejdříve upravit tak, aby rekurzivní volání skutečně nepotřebovalo zásobník. Například lze použít akumulátor a funkci s více aritami (zjednodušeně řečeno – fact lze volat s jedním parametrem či se dvěma parametry, přičemž druhým parametrem bude mezihodnota uložená do akumulátoru):

(defn fact
    ([n]
     (fact n 1))
    ([n acc]
     (if (<= n 1)
         acc
         (recur (dec n) (* acc n)))))

Nyní již vše bude fungovat bez přetečení zásobníku:

user =< (fact 1000)
 
4023872600770937735437024339230039857193748642107146325437999104
2993851239862902059204420848696940480047998861019719605863166687
2994808558901323829669944590997424504087073759918823627727188732
5197795059509952761208749754624970436014182780946464962910563938
8743788648733711918104582578364784997701247663288983595573543251
3185323958463075557409114262417474349347553428646576611667797396
6688202912073791438537195882498081268678383745597317461360853795
3452422158659320192809087829730843139284440328123155861103697680
1357304216168747609675871348312025478589320767169132448426236131
4125087802080002616831510273418279777047846358681701643650241536
9139828126481021309276124489635992870511496497541990934222156683
2572080821333186116811553615836546984046708975602900950537616475
8477284218896796462449451607653534081989013854424879849599533191
0172335555660213945039973628075013783761530712776192684903435262
5200015888535147331611702103968175921510907788019393178114194545
2572238655414610628921879602238389714760885062768629671466746975
6291123408243920816015378088989396451826324367161676217916890977
9911903754031274622289988005195444414282012187361745992642956581
7466283029555702990243241531816172104658320367869061172601587835
2075151628422554026517048330422614397428693306169089796848259012
5458327168226458066526769958652682272807075781391858178889652208
1643483448259932660433676601769996128318607883861502794659551311
5655203609398818061213855860030143569452722420634463179746059468
2573103790084024432438465657245014402821885252470935190620929023
1364932734975655139587205596542287497740114133469627154228458623
7738753823048386568897646192738381490014076731044664025989949022
2221765904339901886018566526485061799702356193897017860040811889
7299183110211712298459016419210688843871218556461249607987229085
1929681937238864261483965738229112312502418664935314397013742853
1926649875337218940694281434118520158014123344828015051399694290
1534830776445690990731524332782882698646027898643211390835062170
9500259738986355427719674282224875758676575234422020757363056949
8825087968928162753848863396909959826280956121450994871701244516
4612603790293091208890869420285106401821543994571568059418727489
9809425474217358240106367740459574178516082923013535808184009699
6372524230560855903700624271243416909004153690105933983835777939
4109700277534720000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
00000000N

4. Opakování části kódu s použitím konstrukce dotimes

V některých částech programu (ve skutečnosti jich však není mnoho) může být poměrně užitečné makro nazvané jednoduše a přitom příhodně dotimes, které dokáže nějaký výraz (formu) opakovat n krát. Přitom toto makro může v každé iteraci (opakování) nastavit zvolenou lokální proměnnou na aktuální hodnotu počitadla, přičemž se hodnota počitadla v první iteraci vždy nastavuje na nulu a v poslední iteraci dosahuje zadaného počtu opakování-1:

user => (doc dotimes)
pixie.stdlib/dotimes
([[i n] & body])
 
Executes the expressions in the body n times.
 
  user => (dotimes [i 3] (println i))
 
  1
  2
  3

Vzdáleně tedy můžeme toto makro považovat za ekvivalent programové smyčky for i in range(n): v programovacím jazyku Python či ekvivalent k počítané smyčce for (int i = 0; i<n; i++) známé z céčka (zde bez možnosti mít lokální proměnnou jako počitadlo), C++, Javy atd. Vzhledem k tomu, že se předpokládá, že forma – tělo smyčky – předaná makru dotimes bude mít nějaký vedlejší efekt, nejedná se sice o čistě funkcionální přístup, nicméně se někdy s použitím makra dotimes můžeme setkat (například v testech).

V jednoduchém demonstračním příkladu, který si ukážeme, se na standardní výstup vypisuje převrácená hodnota celých čísel od 0 do 9. Vedlejším efektem je v tomto případě samotný výpis na standardní výstup funkcí println:

user => (dotimes [i 10] (println (/ 1.0 i)))
inf
1.0
0.5
0.3333333333333333
0.25
0.2
0.16666666666666666
0.14285714285714285
0.125
0.1111111111111111
nil
Poznámka: poslední vypsané nil je návratovou hodnotou samotného makra dotimes, nikoli výsledek poslední iterace).
Poznámka2: Dělení nulou je pro hodnoty s plovoucí řádovou čárkou povolené, výsledkem je buď inf, -inf či nan:
user => (/ 1. 0)
inf
 
user => (/ -1. 0)
-inf
 
user => (/ 0. 0)
nan

Podívejme se nyní na nepatrně složitější příklad, který by se v imperativních programovacích jazycích většinou řešil s využitím dvojice do sebe vnořených počítaných programových smyček. Mějme za úkol vypsat tabulku malé násobilky, tj. všechny výsledky vzniklé vynásobením dvojic celých čísel od 1 do 10. Tento algoritmus je možné velmi snadno realizovat právě s využitím makra dotimes, například následujícím one-linerem:

user => (dotimes [i 10] (dotimes [j 10] (print (* (+ i 1) (+ j 1)) "\t")) (println))

Malou „optimalizací“ v tomto zápisu by byla náhrada výrazů (+ i 1) za volání funkce (inc i) s prakticky shodným významem (alespoň pro čísla typu integer). Mimochodem: jméno funkce inc může evokovat to, že zvyšuje hodnotu svého parametru, což však ve skutečnosti ani není možné, neboť se jedná o běžnou funkci a nikoli o makro či o speciální formu (důležitá je tedy návratová hodnota této funkce):

user => (dotimes [i 10] (dotimes [j 10] (print (* (inc i) (inc j)) "\t")) (println))

Pro větší přehlednost si můžeme výše uvedený one-liner přepsat na správně odsazený program, z něhož je patrné, že se skutečně jedná o ekvivalent dvou do sebe zanořených programových smyček:

(dotimes [i 10]
    (dotimes [j 10]
        (print (* (inc i) (inc j)) "\t"))
    (println))

A zde je již výsledek práce tohoto prográmku (poslední nil je opět návratovou hodnotou makra dotimes):

1     2     3     4     5     6     7     8     9     10
2     4     6     8     10    12    14    16    18    20
3     6     9     12    15    18    21    24    27    30
4     8     12    16    20    24    28    32    36    40
5     10    15    20    25    30    35    40    45    50
6     12    18    24    30    36    42    48    54    60
7     14    21    28    35    42    49    56    63    70
8     16    24    32    40    48    56    64    72    80
9     18    27    36    45    54    63    72    81    90
10    20    30    40    50    60    70    80    90    100
nil

Poznámka: na rozdíl od dále uvedené formy for není možné v makru dotimes použít kombinaci dvou či většího množství počitadel. Následující zápis je tedy bude ignorovat pokus o vytvoření počitadla j:

user => (dotimes [i 10 j 20] (println i))
0
1
2
3
4
5
6
7
8
9
nil
 
user => (dotimes [i 10 j 20] (println j))
ERROR:
 in pixie function repl_fn
 
in pixie/repl.pxi at 27:24
               (let [x (eval form)]
                        ^
in internal function eval
 
in <unknown> at 13:31
(dotimes [i 10 j 20] (println j))
                              ^
RuntimeException: :pixie.stdlib/AssertionException Var j is undefined

5. Průchod prvky sekvence s využitím doseq

Mnohem častěji než s výše uvedeným makrem dotimes se setkáme s makrem doseq, které lze použít pro postupné procházení všemi prvky zvolené sekvence (a tím pádem i kolekce):

user => (doc doseq)
pixie.stdlib/doseq
([binding & body])
 
Evaluates all elements of the seq, presumably for side effects. Returns nil.

Použití tohoto makra je velmi jednoduché, což je ostatně patrné i z následujících příkladů.

Průchod prvky vektoru:

user => (doseq [i ["a" "b" "c" "d"]] (println i))
a
b
c
d

Podobný příklad, ovšem pro seznam:

user => (doseq [i '("a" "b" "c" "d")] (println i))
a
b
c
d

Průchod všemi prvky množiny (prvky se do množiny obecně ukládají neuspořádaně):

user => (doseq [i #{1 2 3 4 5 6}] (print i " "))
4  6  2  5  3  1  nil

Použití pro generované sekvence (ve skutečnosti by bylo mnohem vhodnější tyto příklady přepsat tak, aby se používala funkce vyššího řádu map či makro for):

user => (doseq [dot (repeat 100 ".")] (print dot))
....................................................................................................
 
user => (doseq [i (range 10)] (println (/ i (inc i))))
0
1/2
2/3
3/4
4/5
5/6
6/7
7/8
8/9
9/10
nil

Poznámka: pokud stojíte před takovým problémem, který vyžaduje průchod nějakou sekvencí (či kolekcí) s tím, že výsledkem má být jiná sekvence, není použití makra doseq vhodné, protože to se používá jen tehdy, pokud vyžadujete nějaký vedlejší efekt (zde například volání print či println). Při zpracování jedné sekvence na sekvenci jinou je vhodnější použít for, někdy též map, filter či reduce. Jinými slovy – nenechte se prosím zmást tím, že v některých jiných programovacích jazycích se veškeré průchody sekvencemi provádí jediným typem smyčky for-each, když v Clojure/Pixie existují pro každý typ zpracování specializované funkce (= low-level návrhové vzory).

6. Generátorová notace seznamu – list comprehension

Programovací jazyk Pixie převzal z Clojure i makro for, s jehož využitím je možné používat takzvanou „generátorovou notaci seznamu“, což je poněkud nepřesně přeložený anglický termín „list comprehension“. O způsobu překladu tohoto termínu se vedly a pravděpodobně dodnes vedou poměrně vášnivé diskuse; já se zde budu držet překladu použitého v knize Ponořme se do Python(u) 3 (původně Dive Into Python 3)), kterou si můžete stáhnout na stránkách http://knihy.nic.cz/, popř. si zde objednat i papírovou verzi knihy.

S využitím generátorové notace seznamu je možné v programovacích jazycích, které tento zápis podporují, zapsat deklaraci vytvoření nového seznamu s využitím seznamu jiného, a to pomocí aplikace nějaké zvolené funkce na všechny prvky zdrojového seznamu. V mnoha programovacích jazycích je nutné pro generátorovou notaci seznamů používat zvláštní příkaz či deklaraci, tj. vlastně novou syntaktickou kategorii. Z těch známějších jazyků podporujících list comprehension se jedná například o Python, Haskell, Scala či Erlang.

Programovací jazyk Clojure a tím pádem i Pixie se však od těchto jazyků v podpoře pro generátorovou notaci seznamů liší, a to dokonce hned v několika ohledech. V ClojurePixie se totiž nemusela zavádět žádná nová syntaxe, ale postačovalo vhodně naprogramovat makro (jeho kód je však poměrně dlouhý). Navíc je možné aplikovat zvolenou funkci nejenom na (konečný) seznam, ale na libovolnou (a to i nekonečnou) sekvenci, čímž ve výsledku můžeme taktéž získat nekonečnou sekvenci vyhodnocovanou líně (lazy sequence). Třetím rozdílem Clojure a Pixie oproti jiným jazykům s podporou generátorových notací seznamů je to, že je možné pracovat s libovolným množstvím zdrojových seznamů (přesněji řečeno sekvencí).

Generátorová notace seznamů je implementována již výše zmíněným makrem for:

user => (doc for)
pixie.stdlib/for
([bindings & body])
 
A list comprehension for the bindings.
 
  user => (for [x [1 2 3]] x)
  [1 2 3]
  user => (for [x [1 2 3] y [:a :b :c]] [x y])
  [[1 :a] [1 :b] [1 :c] [2 :a] [2 :b] [2 :c] [3 :a] [3 :b] [3 :c]]

Zajímavý je především způsob zápisu makra for, kde je v hranatých závorkách (tedy ve vektoru) nejprve uveden název lokální proměnné používané při výpočtu a za jménem proměnné je pak uveden zdrojový seznam. Apostrof před zdrojovým seznamem zabrání jeho vyhodnocení; pokud by zde nebyl uveden, došlo by k pokusu o zavolání funkce s názvem 1 s trojicí parametrů 2, 3 4. Posledním parametrem makra for je výraz (forma), jehož výsledek je použit pro konstrukci výsledného seznamu:

user=> (for [x '(1 2 3 4)] (inc x))
(2 3 4 5)

Lokální proměnnou x, která je postupně navazována na jednotlivé prvky zdrojového seznamu, je samozřejmě možné použít i ve složitějších výrazech; důležitý je pro nás pouze výsledek daného výrazu (jen minimálně se zde používají funkce s vedlejším efektem, i když i to je možné).

Kromě seznamu lze použít i libovolnou jinou sekvenci či kolekci (vektor, mapu, množinu):

user => (for [x [1 2 3 4]] (inc x))
(2 3 4 5)
 
user => (for [x #{1 2 3 4}] (inc x))
(5 3 4 2)
 
user => (for [x {:a 1 :b 2 :c 3 :d 4}] (key x))
(:b :c :a :d)
 
user => (for [x (range 1 10)] (/ 3 x))
(3 3/2 1 3/4 3/5 1/2 3/7 3/8 1/3)

for je samozřejmě možné libovolným způsobem zanořovat, i když čitelnost je v tomto případě již velmi malá (lepší je rozdělení zápisu na více řádků):

user=> (for [x (for [x (range 1 10)] (inc x))] (* x 2))
(4 6 8 10 12 14 16 18 20)

for dokáže pracovat s více generátory (tj. s více zdrojovými sekvencemi) současně. Princip jeho práce se podobá funkci vnořených programových smyček typu „for“. Podívejme se nyní, jak se vlastně makro for s více generátory zapisuje. Nejjednodušší způsob zápisu vypadá následovně:

user=> (for [x (range 1 10) y (range 1 10)] (* x y))
(1 2 3 4 5 6 7 8 9
2 4 6 8 10 12 14 16 18
3 6 9 12 15 18 21 24 27
4 8 12 16 20 24 28 32 36
5 10 15 20 25 30 35 40 45
6 12 18 24 30 36 42 48 54
7 14 21 28 35 42 49 56 63
8 16 24 32 40 48 56 64 72
9 18 27 36 45 54 63 72 81)
Poznámka: výpis je samozřejmě upraven ručně, aby bylo patrné, jakým způsobem je výsledná sekvence vytvořena.

My ovšem můžeme taktéž vytvořit sekvenci obsahující dvojice, trojice atd. prvků. Postačuje jen vhodně upravit funkci volanou makrem for, například takovým způsobem, aby se vytvořit vektor obsahující dvojici čísel:

user=> (for [x (range 1 10) y (range 1 10)] [x y])
(
[1 1] [1 2] [1 3] [1 4] [1 5] [1 6] [1 7] [1 8] [1 9]
[2 1] [2 2] [2 3] [2 4] [2 5] [2 6] [2 7] [2 8] [2 9]
[3 1] [3 2] [3 3] [3 4] [3 5] [3 6] [3 7] [3 8] [3 9]
[4 1] [4 2] [4 3] [4 4] [4 5] [4 6] [4 7] [4 8] [4 9]
[5 1] [5 2] [5 3] [5 4] [5 5] [5 6] [5 7] [5 8] [5 9]
[6 1] [6 2] [6 3] [6 4] [6 5] [6 6] [6 7] [6 8] [6 9]
[7 1] [7 2] [7 3] [7 4] [7 5] [7 6] [7 7] [7 8] [7 9]
[8 1] [8 2] [8 3] [8 4] [8 5] [8 6] [8 7] [8 8] [8 9]
[9 1] [9 2] [9 3] [9 4] [9 5] [9 6] [9 7] [9 8] [9 9])
Důležitá poznámka: na rozdíl od jazyka Clojure prozatím nelze v Pixie použít modifikátory :when a :while, což je nepochybně škoda.

7. Praktický příklad – výpočet Eulerova čísla s využitím doseq, forreduce

Pojďme si nyní ukázat použití funkcí a maker doseq, for a reduce v jednoduchém programu pro výpočet přibližné hodnoty Eulerova čísla, které tvoří základ přirozených logaritmů a má v matematice jako jedna ze základních konstant i mnoho dalších použití. Pro výpočet Eulerova čísla použijeme součet nekonečné číselné řady prvků s hodnotou 1/x!. Celý vzorec lze nalézt na stránce http://cs.wikipedia.org/wi­ki/Eulerovo_číslo ve formě druhé definice této konstanty.

Aby bylo možné provést součet prvků výše zmíněné nekonečné řady, musíme nejprve deklarovat funkci pro výpočet faktoriálu. Některé způsoby zápisu této funkce jsme si již ukazovali – jednalo se jak o čistě rekurzivní zápis, tak i o zápis používající tail rekurzi, popř. programovou smyčku. Nyní se pro jednoduchost spolehneme na použití funkce reduce, která bude redukovat vypočtenou sekvenci celočíselných hodnot (aritmetickou řadu celých čísel od 1 do n) s využitím operace násobení (jedná se o pravděpodobně nejkratší zápis faktoriálu):

(defn fact
    [n]
    (reduce *
        (range 1 (inc n))))

Námi vytvořenou funkci pro výpočet faktoriálu můžeme jednoduše otestovat, například její aplikací na vstupní hodnoty 0..9:

user=> (map fact (range 0 10))
 
(1 1 2 6 24 120 720 5040 40320 362880)

Funkci fact můžeme použít společně s makrem for pro vytvoření číselné řady obsahující hodnoty 1/x!, přičemž x leží ve zvoleném rozsahu (0..max):

user=> (for [x (range 0 10)] (/ 1 (fact x)))
 
(1 1 1/2 1/6 1/24 1/120 1/720 1/5040 1/40320 1/362880)

Pro výpočet přibližné hodnoty Eulerova čísla nám již pouze postačí výsledné hodnoty sečíst, například takto:

user=> (reduce + (for [x (range 0 10)] (/ 1 (fact x))))
98641/36288
Poznámka: povšimněte si, že se výsledek vyjádřil formou zjednodušeného zlomku, protože jsme dělili dvě celá čísla.

Samozřejmě je možné si vypsat tabulku pro zvětšující se limit, aby bylo patrné, že se výpočet postupně přibližuje k Eulerovu číslu (pokud ovšem nedojde k přetečení při zjednodušování zlomků!):

(doseq [i (range 1 15)]
    (println i
        (reduce + (for [x (range 0 i)]
            (/ 1 (fact x))))))
 
1 1
2 2
3 5/2
4 8/3
5 65/24
6 163/60
7 1957/720
8 685/252
9 109601/40320
10 98641/36288
11 9864101/3628800
12 13563139/4989600
13 260412269/95800320
14 8463398743/3113510400

Zápis výsledků pomocí zlomků nám v tomto případě příliš nepomůže, takže zlomek převedeme na běžnou numerickou hodnotu reprezentovanou v systému plovoucí řádové čárky. Jednou z možností je pouhé přičtení hodnoty 0.0 popř. vynásobení hodnotou 1.0:

(doseq [i (range 1 15)]
    (println i (+ 0.0
        (reduce + (for [x (range 0 i)]
            (/ 1 (fact x)))))))
 
1 1.000000
2 2.000000
3 2.500000
4 2.666667
5 2.708333
6 2.716667
7 2.718056
8 2.718254
9 2.718279
10 2.718282
11 2.718282
12 2.718282
13 2.718282
14 2.718282

8. Použití programové konstrukce loop/recur

Poměrně jednoduše je možné rekurzi s využitím TCO (tail call optimization) deklarovat pomocí loop společně s recur. Za formou loop se nachází vektor s deklarací a inicializací lokálních proměnných použitých ve smyčce, na kterou je rekurzivní volání automaticky převedeno. Forma recur umístěná uvnitř loop přesune řízení programu ihned za slovo loop, přičemž parametry předané do recur slouží ke změně hodnot(y) lokálních proměnných. Co to znamená v praxi? Podívejme se na způsob zápisu smyčky, jejíž počitadlo (lokální proměnná i) se zvyšuje od 0 do 10:

; počitadlo od 0 do 10
(loop [i 0]
    (if (= i 10)           ; podmínka pro ukončení smyčky
        i                  ; návratová hodnota při splnění podmínky
        (recur (+ i 1))))  ; rekurze (s TCO)
                           ; v nové iteraci obsahuje i hodnotu staré_i+1

Poznámka: díky tomuto způsobu zápisu si může překladač pohlídat, zda se skutečně může použít TCO.

Smyčka, ve které se používá počitadlo i a současně se počítá i suma sum, bude implementována takto:

; výpočet desáté mocniny dvojky
(loop [i 0 sum 1]
    (if (= i 10)                     ; podmínka pro ukončení smyčky
        sum                          ; návratová hodnota při splnění podmínky
        (recur (+ i 1) (* sum 2))))  ; rekurze (s TCO)
                                     ; v nové iteraci obsahuje i hodnotu staré_i+1
                                     ; a sum hodnotu staré_sum * 2

Podívejme se nyní na implementaci funkce pro výpočet n-té mocniny dvojky:

; funkce pro výpočet n-té mocniny dvojky
(defn pow2
    [n]
    (loop [i 0 sum 1]
        (if (= i n)        ; podmínka pro ukončení smyčky
            sum            ; návratová hodnota při splnění podmínky
            (recur (+ i 1) (* sum 2)))))  ; rekurze (s TCO)
                           ; v nové iteraci obsahuje i hodnotu staré_i+1
                           ; a sum hodnotu staré_sum * 2

Mimochodem: s využitím loop a recur bylo vytvořeno i makro while, jehož kód vypadá následovně (ve skutečnosti toto makro není v Pixie příliš užitečné, na rozdíl od mainstreamových jazyků a jejich programové konstrukce while):

; definice makra while
(defmacro while
  "Repeatedly executes body while test expression is true. Presumes
  some side-effect will cause test to become false/nil. Returns nil"
  {:added "1.0"}
  [test & body]
  `(loop []
      (when ~test
          ~@body
          (recur))))

9. Použití FFI aneb volání nativních funkcí

Knihovny jazyka Pixie nejsou v porovnání s některými jinými programovacími jazyky nijak rozsáhlé, což však nemusí vždy vadit, neboť Pixie dokáže volat i nativní funkce přes FFI. Vlastně se zde setkáváme s podobným principem, jaký známe i z Clojure – Clojure dokáže využívat knihovny nabízené VM (JVM, CLR…), Pixie dokáže využívat nativní knihovny nabízené operačním systémem. V obou případech není zapotřebí znovuvynalézat kolo.

Vlastní rozhraní pro FFI je implementováno ve jmenném prostoru ffi-infer, který však většinou není nutné importovat explicitně – to za nás udělá příslušný modul s obalovými funkcemi (pixie.math, pixie.io, pixie.stdlib, pixie.uv, pixie.streams.zip atd.).

Již v základní instalaci Pixie (viz též úvodní část tohoto článku) jsou k dispozici některé základní nativní knihovní funkce z knihovny libc. Pokud některé z těchto funkcí potřebujeme volat, je nejdříve nutné načíst i příslušný jmenný prostor (namespace). Příkladem mohou být matematické funkce, jejichž nativní verze jsou přístupné přes jmenný prostor pixie.math. Jestliže tyto funkce potřebujeme volat, stačí použít direktivu :require uvedenou při specifikaci nového jmenného prostoru (se jmennými prostory se pracuje stejně, jako je tomu v programovacím jazyku Clojure):

test => (ns test (:require [pixie.math :as math]))
nil
 
test => (math/abs -10)
10
 
test => (math/sin 3.1415)
0.000093
 
test => (math/sin (/ 3.1415 2))
1.000000
 
test => math/M_PI
3.141590
 
test => math/M_SQRT2
1.414210
 
test => (* math/M_SQRT2 math/M_SQRT2)
1.999990

Alternativně je možné určit, že všechny symboly z načítaného jmenného prostoru budou dostupné i bez nutnosti udání jmenného prefixu:

test => (ns test (:require [pixie.math :refer :all])
nil
 
test => (abs -42)
42
 
test => M_PI
3.141590
 
test => M_SQRT2
1.414210
 
test => (sin M_PI)
0.000003
 
test => (sin (/ M_PI 2))
1.000000

10. Nativní funkce dostupné ze standardních modulů jazyka Pixie

Mezi další užitečné funkce, které používají FFI pro volání nativního kódu, patří funkce ze jmenného prostoru io. Kromě klasických funkcí typu spit a slurp zde nalezneme například užitečnou funkci run-command, která nejenže spustí externí příkaz, ale vrátí i jeho výstup (tím se dosti podstatným způsobem liší od funkce exec, která dokáže vrátit jen návratový kód, nikoli výstup):

test => (ns test (:require [pixie.io :refer :all]))
nil
 
test => (count (slurp "README.md"))
5961
 
test => (run-command "date")
"Po rij 24 19:34:17 CEST 2016\n"
 
test => (run-command "ls -la")
... dlouhý výstup...

Pokud potřebujete zpracovávat soubory po jednotlivých řádcích (nikoli tedy jako sekvence), mohou se hodit funkce open-read (otevření souboru pro čtení), open-write (otevření souboru pro zápis), read-line (načtení jednoho řádku z otevřeného souboru) atd.:

test => (def fin (open-read "README.md"))
<inst pixie.stdlib.Var>
 
test => (read-line fin)
"# Pixie"
test => (fs_close fin)
 

Alternativně lze získat sekvenci řádků:

test => (line-seq fin)
 

Samozřejmě je možné naimportovat větší množství modulů současně:

test => (ns test (:require [pixie.io :refer :all] [pixie.string :as str]))
nil

Načtení textového souboru s jeho rozdělením na jednotlivé řádky lze poměrně čitelně vyřešit threading makrem:

(-> "README.md" slurp str/split-lines)

Délky jednotlivých řádků v souboru README.md vypočteme opět s použitím threading makra, tentokrát však musíme dbát na to, že do funkce vyššího řádu map se sekvence předává ve druhém parametru, nikoli v parametru prvním:

test => (->> "README.md" slurp str/split-lines (map #(count %)))
(238 7 0 208 0 8 0 166 266 0 11 0 38 0 26 32 69 86 10 25 0 15 0
26 47 46 68 75 0 11 0 23 14 0 191 0 0 20 0 28 0 0 11 0 46 27 0
30 0 0 13 127 0 6 0 33 0 483 0 0 43 0 144 0 112 0 270 0 250 0 10
0 103 84 0 21 23 0 11 18 5 26 0 3 0 0 0 338 0 28 0 374 0 108 0
115 0 29 60 0 15 0 286 0 23 0 363 0 13 181 0 10 0 251 0)

11. Příklad – vygenerování perexového obrázku k tomuto článku

Jako příklad použití některých funkcí z modulů pixie.math a pixie.io (tyto funkce volají svůj nativní protějšek) si ukažme krátký prográmek, který vygeneruje perexový obrázek k tomuto článku. Při výpočtu vektorového obrázku ukládaného do formátu SVG se používají funkce sinus a kosinus, při ukládání obrázku pak funkce spit (opak funkce slurp, těžko říct, proč Rich Hickey zvolil taková libozvučná jména):

(ns logo (:require [pixie.math :refer :all]
                   [pixie.io :refer :all]))
 
(def s 480)
 
(->>
    (str "<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='" s "' height='" s "'>"
    (loop [i 0 R 255 G 255 B 0 o ""]
        (let [r (- 128 i)
              a (/ i 12.)
              b (+ i 80)
              x (+ (/ s 2) (* b (cos a)))
              y (+ (/ s 2) (* b (sin a)))
              c (str R "," G "," B)
              p (str "<circle cx='" x "' cy='" y "' r='" r "' ")
              q (str "fill='rgb(" R "," G "," B ")' style='fill-opacity:.06'/>\n")]
              (if (< i 128)
                  (recur (inc i) (- R 2) G (+ B 2) (str o p q p "fill='none' stroke='black'/>\n"))
                  o)))
         "</svg>")
    (spit "logo.svg"))

Obrázek 1: Vektorový obrazec vytvořený předchozím demonstračním příkladem.

12. Problém s nativními funkcemi typu printf

Volání některých nativních funkcí je – alespoň prozatím – v Pixie nepoužitelné. Jedná se především o funkci printf, což je škoda, protože právě tato funkce je v praxi velmi užitečná. Pro ilustraci se podívejme na to, co se stane při volání této funkce. V případě, že formátovací řetězec neobsahuje žádné formátovací příkazy, bude volání úspěšné a funkce printf dokonce korektně vrátí počet vypsaných znaků:

bitcoin_skoleni

user => (printf "Hello World\n")
Hello World
12

Při pokusu o použití formátovacích znaků a předání parametrů však ke kýženým výsledkům nedojdeme:

user => (printf "%d %d\n" 1 2)
5175984 -959918736
19
 
user => (printf "result: %f\n" 3.14)
result: 0.000000
17
 
user => (printf "result: %+10d %10.7f\n" 1 1.2)
result:   +5175984  0.0000000
30
 
user => (printf "*%s*" "hello")
*garbage*8

Tato chyba je vývojářům známá a je popsána ve dvou issues:

  1. printf doesn't work as expected #420
  2. ffi :variadic? true generated function do not use all arguments supplied #449

13. Odkazy na předchozí části tohoto seriálu

  1. Clojure 1: Úvod
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm/
  2. Clojure 2: Symboly, kolekce atd.
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-2-cast/
  3. Clojure 3: Funkcionální programování
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-3-cast-funkcionalni-programovani/
  4. Clojure 4: Kolekce, sekvence a lazy sekvence
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-4-cast-kolekce-sekvence-a-lazy-sekvence/
  5. Clojure 5: Sekvence, lazy sekvence a paralelní programy
    http://www.root.cz/clanky/clojure-a-bezpecne-aplikace-pro-jvm-sekvence-lazy-sekvence-a-paralelni-programy/
  6. Clojure 6: Podpora pro paralelní programování
    http://www.root.cz/clanky/programovaci-jazyk-clojure-6-futures-nejsou-jen-financni-derivaty/
  7. Clojure 7: Další funkce pro paralelní programování
    http://www.root.cz/clanky/programovaci-jazyk-clojure-7-dalsi-podpurne-prostredky-pro-paralelni-programovani/
  8. Clojure 8: Identity, stavy, neměnné hodnoty a reference
    http://www.root.cz/clanky/programovaci-jazyk-clojure-8-identity-stavy-nemenne-hodnoty-a-referencni-typy/
  9. Clojure 9: Validátory, pozorovatelé a kooperace s Javou
    http://www.root.cz/clanky/programovaci-jazyk-clojure-9-validatory-pozorovatele-a-kooperace-mezi-clojure-a-javou/
  10. Clojure 10: Kooperace mezi Clojure a Javou
    http://www.root.cz/clanky/programovaci-jazyk-clojure-10-kooperace-mezi-clojure-a-javou-pokracovani/
  11. Clojure 11: Generátorová notace seznamu/list comprehension
    http://www.root.cz/clanky/programovaci-jazyk-clojure-11-generatorova-notace-seznamu-list-comprehension/
  12. Clojure 12: Překlad programů z Clojure do bajtkódu JVM I:
    http://www.root.cz/clanky/programovaci-jazyk-clojure-12-preklad-programu-z-clojure-do-bajtkodu-jvm/
  13. Clojure 13: Překlad programů z Clojure do bajtkódu JVM II:
    http://www.root.cz/clanky/programovaci-jazyk-clojure-13-preklad-programu-z-clojure-do-bajtkodu-jvm-pokracovani/
  14. Clojure 14: Základy práce se systémem maker
    http://www.root.cz/clanky/programovaci-jazyk-clojure-14-zaklady-prace-se-systemem-maker/
  15. Clojure 15: Tvorba uživatelských maker
    http://www.root.cz/clanky/programovaci-jazyk-clojure-15-tvorba-uzivatelskych-maker/
  16. Clojure 16: Složitější uživatelská makra
    http://www.root.cz/clanky/programovaci-jazyk-clojure-16-slozitejsi-uzivatelska-makra/
  17. Clojure 17: Využití standardních maker v praxi
    http://www.root.cz/clanky/programovaci-jazyk-clojure-17-vyuziti-standardnich-maker-v-praxi/
  18. Clojure 18: Základní techniky optimalizace aplikací
    http://www.root.cz/clanky/programovaci-jazyk-clojure-18-zakladni-techniky-optimalizace-aplikaci/
  19. Clojure 19: Vývojová prostředí pro Clojure
    http://www.root.cz/clanky/programovaci-jazyk-clojure-19-vyvojova-prostredi-pro-clojure/
  20. Clojure 20: Vývojová prostředí pro Clojure (Vimu s REPL)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-20-vyvojova-prostredi-pro-clojure-integrace-vimu-s-repl/
  21. Clojure 21: ClojureScript aneb překlad Clojure do JS
    http://www.root.cz/clanky/programovaci-jazyk-clojure-21-clojurescript-aneb-preklad-clojure-do-javascriptu/
  22. Leiningen: nástroj pro správu projektů napsaných v Clojure
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure/
  23. Leiningen: nástroj pro správu projektů napsaných v Clojure (2)
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-2/
  24. Leiningen: nástroj pro správu projektů napsaných v Clojure (3)
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-3/
  25. Leiningen: nástroj pro správu projektů napsaných v Clojure (4)
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-4/
  26. Leiningen: nástroj pro správu projektů napsaných v Clojure (5)
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-5/
  27. Leiningen: nástroj pro správu projektů napsaných v Clojure (6)
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-6/
  28. Programovací jazyk Clojure a databáze (1.část)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-databaze-1-cast/
  29. Pluginy pro Leiningen
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-pluginy-pro-leiningen/
  30. Programovací jazyk Clojure a knihovny pro práci s vektory a maticemi
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-knihovny-pro-praci-s-vektory-a-maticemi/
  31. Programovací jazyk Clojure a knihovny pro práci s vektory a maticemi (2)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-knihovny-pro-praci-s-vektory-a-maticemi-2/
  32. Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk
    http://www.root.cz/clanky/programovaci-jazyk-clojure-synteza-proceduralnich-textur-s-vyuzitim-knihovny-clisk/
  33. Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk (2)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-synteza-proceduralnich-textur-s-vyuzitim-knihovny-clisk-2/
  34. Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure
    http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure/
  35. Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure (2)
    http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure-2/
  36. Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure (3)
    http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure-3/
  37. Programovací jazyk Clojure a práce s Gitem
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem/
  38. Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk (dokončení)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-synteza-proceduralnich-textur-s-vyuzitim-knihovny-clisk-dokonceni/
  39. Programovací jazyk Clojure a práce s Gitem (2)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem-2/
  40. Programovací jazyk Clojure – triky při práci s řetězci
    http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-retezci/
  41. Programovací jazyk Clojure – triky při práci s kolekcemi
    http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-kolekcemi/
  42. Programovací jazyk Clojure – práce s mapami a množinami
    http://www.root.cz/clanky/programovaci-jazyk-clojure-prace-s-mapami-a-mnozinami/
  43. Programovací jazyk Clojure – základy zpracování XML
    http://www.root.cz/clanky/programovaci-jazyk-clojure-zaklady-zpracovani-xml/
  44. Programovací jazyk Clojure – testování s využitím knihovny Expectations
    http://www.root.cz/clanky/programovaci-jazyk-clojure-testovani-s-vyuzitim-knihovny-expectations/
  45. Programovací jazyk Clojure – některé užitečné triky použitelné (nejenom) v testech
    http://www.root.cz/clanky/programovaci-jazyk-clojure-nektere-uzitecne-triky-pouzitelne-nejenom-v-testech/
  46. Enlive – výkonný šablonovací systém pro jazyk Clojure
    http://www.root.cz/clanky/enlive-vykonny-sablonovaci-system-pro-jazyk-clojure/
  47. Nástroj Leiningen a programovací jazyk Clojure: tvorba vlastních knihoven pro veřejný repositář Clojars
    http://www.root.cz/clanky/nastroj-leiningen-a-programovaci-jazyk-clojure-tvorba-vlastnich-knihoven-pro-verejny-repositar-clojars/
  48. Novinky v Clojure verze 1.8.0
    http://www.root.cz/clanky/novinky-v-clojure-verze-1–8–0/
  49. Asynchronní programování v Clojure s využitím knihovny core.async
    http://www.root.cz/clanky/asynchronni-programovani-v-clojure-s-vyuzitim-knihovny-core-async/
  50. Asynchronní programování v Clojure s využitím knihovny core.async (pokračování)
    http://www.root.cz/clanky/asynchronni-programovani-v-clojure-s-vyuzitim-knihovny-core-async-pokracovani/
  51. Asynchronní programování v Clojure s využitím knihovny core.async (dokončení)
    http://www.root.cz/clanky/asynchronni-programovani-v-clojure-s-vyuzitim-knihovny-core-async-dokonceni/
  52. Vytváříme IRC bota v programovacím jazyce Clojure
    http://www.root.cz/clanky/vytvarime-irc-bota-v-programovacim-jazyce-clojure/
  53. Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
    https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/
  54. Multimetody v Clojure aneb polymorfismus bez použití OOP
    https://www.root.cz/clanky/multimetody-v-clojure-aneb-polymorfismus-bez-pouziti-oop/
  55. Práce s externími Java archivy v programovacím jazyku Clojure
    https://www.root.cz/clanky/prace-s-externimi-java-archivy-v-programovacim-jazyku-clojure/
  56. Pixie: lehký skriptovací jazyk s „kouzelnými“ schopnostmi
    https://www.root.cz/clanky/pixie-lehky-skriptovaci-jazyk-s-kouzelnymi-schopnostmi/

14. Odkazy na Internetu

  1. Pixie (repositář na GitHubu)
    https://github.com/pixie-lang/pixie
  2. Pixie Installation
    https://github.com/pixie-lang/pixie/wiki/Installation
  3. Pixie Libraries
    https://github.com/pixie-lang/pixie/wiki/Libraries
  4. Interview with Timothy Baldridge, Pixie's language creator
    https://notamonadtutorial.com/indie-languages-interview-pixie-and-timothy-baldridge-cadbc36418dc#.vhbl5rp1c
  5. Pixie (programming language) [Wikipedia]
    https://en.wikipedia.org/wi­ki/Pixie_%28programming_lan­guage%29
  6. FFI or interop with C libraries
    https://github.com/pixie-lang/pixie/wiki/FFI-%28interop-with-C%29
  7. printf doesn't work as expected #420
    https://github.com/pixie-lang/pixie/issues/420
  8. ffi :variadic? true generated function do not use all arguments supplied #449
    https://github.com/pixie-lang/pixie/issues/449
  9. Clojure home page
    http://clojure.org/
  10. Clojure (downloads)
    http://clojure.org/downloads
  11. Clojure – Functional Programming for the JVM
    http://java.ociweb.com/mar­k/clojure/article.html
  12. Clojure quick reference
    http://faustus.webatu.com/clj-quick-ref.html
  13. 4Clojure
    http://www.4clojure.com/
  14. ClojureDoc (rozcestník s dokumentací jazyka Clojure)
    http://clojuredocs.org/
  15. Clojure (na Wikipedia EN)
    http://en.wikipedia.org/wiki/Clojure
  16. Clojure (na Wikipedia CS)
    http://cs.wikipedia.org/wiki/Clojure
  17. Clojars:
    https://clojars.org/
  18. Seznam knihoven na Clojars:
    https://clojars.org/projects
  19. Zip archiv s Clojure 1.8.0
    http://repo1.maven.org/ma­ven2/org/clojure/clojure/1­.8.0/clojure-1.8.0.zip
  20. Clojure 1.8 is now available
    http://clojure.org/news/2016/01/19/clo­jure18
  21. Changes to Clojure in Version 1.8
    https://github.com/clojure/clo­jure/blob/master/changes.md
  22. Clojure core.async
    http://www.infoq.com/presen­tations/clojure-core-async
  23. core.async API Reference
    https://clojure.github.io/core.async/
  24. Clojure core.async Channels
    http://clojure.com/blog/2013/06/28/clo­jure-core-async-channels.html
  25. core.async examples
    https://github.com/clojure/co­re.async/blob/master/exam­ples/walkthrough.clj
  26. Timothy Baldridge – Core.Async
    https://www.youtube.com/wat­ch?v=enwIIGzhahw
  27. List comprehension
    http://en.wikipedia.org/wi­ki/List_comprehension
  28. List Comprehensions in Clojure
    http://asymmetrical-view.com/2008/11/18/list-comprehensions-in-clojure.html
  29. Clojure Programming Concepts: List Comprehension
    http://en.wikibooks.org/wi­ki/Clojure_Programming/Con­cepts#List_Comprehension

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.