Programovací jazyk Clojure – triky při práci s řetězci

Pavel Tišnovský 30. 7. 2015

Dvacátá část seriálu o programovacím jazyku Clojure i o knihovnách, které jsou pro tento jazyk dostupné, se od předchozích částí odlišuje, protože se nebudeme zabývat popisem nových knihoven, ale vrátíme se na samotné začátky programování v Clojure. Zmíníme se totiž o některých tipech a tricích při práci s řetězci.

Obsah

1. Programovací jazyk Clojure – triky při práci s řetězci

2. Funkce pro práci s řetězci ve jmenném prostoru clojure.string

3. Metody třídy java.lang.String

4. Konstrukce řetězce z několika částí

5. Test, zda je řetězec prázdný

6. Převody mezi verzálkami a minuskami

7. Funkce clojure.string/escape

8. Funkce clojure.string/replace

9. Funkce pro spojování a rozdělování řetězců

10. Řetězce chápané jako sekvence znaků

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

12. Odkazy na Internetu

1. Programovací jazyk Clojure – triky při práci s řetězci

Již v perexu tohoto článku bylo napsáno, že se v dnešní části seriálu o programovacím jazyce Clojure i o knihovnách, které pro tento jazyk existují, budeme zabývat popisem některých tipů a triků využitelných při práci s řetězci. Je tomu tak ze dvou důvodů – řetězce a jejich zpracování jsou nedílnou součástí většiny nenumerických algoritmů a navíc jsem z dotazů zjistil, že i ti vývojáři, kteří v jazyku Clojure začali programovat, mnohdy nevyužívají všech možností nabízených zejména funkcemi ve jmenném prostoru clojure.string a zbytečně pak musí volat metody třídy java.lang.String (tedy využívat tzv. java interop), což ovšem není totéž (už jen z toho důvodu, že většina funkcí pro práci s řetězci bez problému „přežije“ předání hodnoty nil, zatímco v případě javovských metod tomu tak pochopitelně není).

Důležité je si ihned na začátku práce s řetězci uvědomit, že řetězce jsou – ostatně podobně jako je tomu i v Javě – konstantní a tudíž i neměnné (immutable), což sice v některých případech může vést k tvorbě neefektivních operací, kterým se však lze v Clojure většinou zcela vyhnout. To, že jsou řetězce neměnné, však zjednodušuje tvorbu bezpečných vícevláknových aplikací, řetězce lze efektivně využívat jako klíče do asociativních polí atd. atd., takže přednosti většinou převažují nad zápory. Pokud by tomu tak nebylo a některý algoritmus by skutečně vyžadoval modifikovatelné řetězce, je možné využít možností knihoven java.lang.StringBuffer a java.lang.StringBuilder [1] [2].

2. Funkce pro práci s řetězci ve jmenném prostoru clojure.string

Většina užitečných funkcí pro práci s řetězci se nachází ve jmenném prostoru clojure.string. Jedná se o následující funkce:

# Funkce Kapitola
1 blank? 5
2 capitalize 6
3 escape 7
4 join 9
5 lower-case 6
6 re-quote-replacement  
7 replace 8
8 replace-first 8
9 reverse  
10 split 9
11 split-lines 9
12 trim  
13 trim-newline  
14 triml  
15 trimr  
16 upper-case 6

V následujících kapitolách budu předpokládat, že prvním příkazem skriptu bude načtení jmenného prostoru clojure.string s aliasem str:

user=> (require '[clojure.string :as str])
nil

Poznámka: nepoužívejte příkaz use namísto require, protože by došlo ke smíchání symbolů z načítaného modulu se symboly vaší aplikace, což znesnadňuje ladění (aneb „use considered harmful“.

3. Metody třídy java.lang.String

Kromě výše zmíněných funkcí dostupných ze jmenného prostoru clojure.string lze v programovacím jazyce Clojure díky existenci tzv. Java interop volat i metody třídy java.lang.String. Platí přitom, že řetězec vytvořený v Clojure či řetězcový literál je současně i javovským řetězcem se všemi z toho vyplývajícími vlastnostmi a omezeními (kódování UTF-16 atd.):

user=> (type "Hello World")
java.lang.String

Připomeňme si, že volání metod nějakého javovského objektu vypadá následovně:

(.názevMetody objekt parametry)

Takže lze například použít formu:

user=> (.length "Hello world!")
12
 
user=> (.getClass "Hello world!")
java.lang.String
 
user=> (.substring "Hello world!" 5 8)
" wo"
 
user=> (.hashCode "Hello world!")
-52966915
 
user=> (.charAt "Hello world!" 11)
\!

Popis všech metod třídy java.lang.String lze nalézt na adrese http://docs.oracle.com/ja­vase/7/docs/api/java/lang/Strin­g.html.

4. Konstrukce řetězce z několika částí

Poměrně často se setkáme s nutností zkonstruovat řetězec z několika částí popř. převést jiný objekt (přesněji řečeno jinou hodnotu) na řetězec. Zde je řešení snadné, protože je možné použít víceúčelovou funkci nazvanou str. Tato funkce akceptuje libovolný počet parametrů libovolných typů, dokonce se ani nemusíte bát ji zavolat s hodnotou nil (ostatně předávání této hodnoty do mnoha funkcí bez kontroly je pro jazyk Clojure idiomatické, na rozdíl od Javy):

user=> (str nil)
""
 
user=> (str 1 2 3)
"123"
 
user=> (str [1 2 3])
"[1 2 3]"
 
user=> (str "first" "second" "third")
"firstsecondthird"
 
user=> (str "first" "second")
"firstsecond"
 

Zastavme se u chvíli u posledního příkladu. V mnoha aplikacích potřebujeme vypsat na standardní výstup nějaké hodnoty, což (samozřejmě) zajistí funkce println:

user=> (println "first" "second")
first second
nil

Pokud z nějakého důvodu nevyhovuje oddělení hodnot mezerou, pomůže právě funkce str:

user=> (println (str "first" "second"))
firstsecond
nil

5. Test, zda je řetězec prázdný

Pro test, zda je řetězec prázdný, je možné použít buď metodu .isEmpty třídy java.lang.String, funkci empty? nebo funkci clojure.string/blank? (povšimněte si otazníku na konci názvu této funkce). Čím se tyto možnosti od sebe odlišují? Hlavním rozdílem je, že clojure.string/blank? bez problémů akceptuje hodnotu nil (považovanou za prázdný řetězec), což je v mnoha aplikacích preferováno. Další rozdíl spočívá v tom, že clojure.string/blank? vrací pravdivostní hodnotu true i pro řetězce obsahující pouze bílé znaky, na rozdíl od empty?:

user=> (empty? nil)
true
 
user=> (empty? "")
true
 
user=> (empty? " ")
false
 
user=> (empty? "Hello world!")
false
user=> (str/blank? nil)
true
 
user=> (str/blank? "")
true
 
user=> (str/blank? " ")
true
 
user=> (str/blank? "Hello world!")
false
user=> (.isEmpty nil)
NullPointerException   clojure.lang.Reflector.invokeNoArgInstanceMember (Reflector.java:301)
 
user=> (.isEmpty "")
true
 
user=> (.isEmpty " ")
false
 
user=> (.isEmpty "Hello world!")
false

6. Převody mezi verzálkami a minuskami

V některých aplikacích je nutné převádět minusky na verzálky a naopak. Opět je možné si vybrat mezi metodami třídy java.lang.String a funkcemi jmenného prostoru clojure.string. Podívejme se ihned na příklady, z nichž je použití těchto funkcí pravděpodobně zřejmé:

user=> (.toLowerCase "Hello world!")
"hello world!"
 
user=> (str/upper-case "Hello world!")
"HELLO WORLD!"
 
user=> (str/lower-case "Hello world!")
"hello world!"
 
user=> (str/capitalize "Hello world!")
"Hello world!"
 
user=> (str/capitalize "HeLLo wOrld!")
"Hello world!

A jak jsme na tom s nabodeníčky? Docela dobře, však jsme v 21. století:

user=> (str/upper-case "Příliš žluťoučký kůň úpěl ďábelské ódy")
"PŘÍLIŠ ŽLUŤOUČKÝ KŮŇ ÚPĚL ĎÁBELSKÉ ÓDY"
 
user=> (str/lower-case "Příliš žluťoučký kůň úpěl ďábelské ódy")
"příliš žluťoučký kůň úpěl ďábelské ódy"
 
user=> (str/capitalize "Příliš žluťoučký kůň úpěl ďábelské ódy")
"Příliš žluťoučký kůň úpěl ďábelské ódy"

7. Funkce clojure.string/escape

Konečně se dostáváme k zajímavějším funkcím. Poměrně užitečnou funkcí je funkce nazvaná clojure.string/escape, které lze předat převodní tabulku (znak na znak, znak na řetězec atd.); jedná se vlastně o určitou obdobu funkce tr. Zmíněná převodní tabulka má podobu mapy:

user=> (str/escape "Hello world!" {\l "*"})
"He**o wor*d!"
 
user=> (str/escape "Hello world!" {\l "*" \o \-})
"He**- w-r*d!"

Podívejme se na použití této funkce ve skutečné aplikaci, konkrétně na místě, kde je zapotřebí nahrazovat trojici znaků, které mají speciální význam v XML či HTML:

(defn escape-xml-chars
    [string]
    (clojure.string/escape string
        {\< "&lt;", \> "&gt;", \& "&amp;"}))
 
(escape-xml-chars "Hello <world>")
"Hello &lt;world&gt;

8. Funkce clojure.string/replace

Jedna z nejužitečnějších funkcí při práci s řetězci je funkce clojure.string/replace. V nejjednodušší podobě se této funkci předává řetězec, nahrazovaná sekvence znaků a řetězec, který se použije namísto nahrazované sekvence znaků:

user=> (str/replace "Hello world!" " " "_")
"Hello_world!"
 
user=> (str/replace "Hello world!" "o" "0")
"Hell0 w0rld!"
 
user=> (str/replace "Hello world!" " " "___")
"Hello___world!"
 
user=> (str/replace "Hello world!" "world" "Clojure")
"Hello Clojure!"

V alternativní podobě je možné namísto nahrazovaného řetězce použít regulární výraz. Připomeňme si, že pro regulární výrazy v Clojure existuje speciální zápis se znakem #. Díky tomu není nutné mnoho znaků ve výrazu quotovat tak, jako je tomu v mnoha dalších programovacích jazycích:

user=> (str/replace "Hello world!" #"[a-z]" ".")
"H.... .....!"
 
user=> (str/replace "Hello world!" #"[A-Za-z]+" "[word]")
"[word] [word]!"
 
user=> (str/replace "Hello world!" #"\w+" "[word]")
"[word] [word]!"
 
user=> (str/replace "Hello world!" #"\s" "_")
"Hello_world!"

Nejzajímavější je ovšem třetí podoba, kde se namísto nahrazovacího řetězce použije funkce, typicky funkce anonymní. V této funkci je možné použít speciální jména parametrů %1 atd., do nichž se uloží hodnoty zachycené do příslušného registru. V následujícím příkladu se *slovo* zvýrazní jako slovo, podobně jako je tomu v AsciiDocu (povšimněte si použití zpětného lomítka před znakem hvězdičky):

user=> (str/replace "Hello world!" #"\*(\w+)\*" #(str "<strong>" (second %1) "</strong>"))
"Hello world!"
 
user=> (str/replace "Hello *world*!" #"\*(\w+)\*" #(str "<strong>" (second %1) "</strong>"))
"Hello <strong>world</strong>!"
 
user=> (str/replace "*Hello* *world*!" #"\*(\w+)\*" #(str "<strong>" (second %1) "</strong>"))
"<strong>Hello</strong> <strong>world</strong>!"
 

Kdo se ještě neztratil, může si vyzkoušet následující regulární výraz, který bude posléze využitý:

(def http-regexp
    #"https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)")
 
user=> (re-find http-regexp "xyzzy")
nil
 
user=> (re-find http-regexp "Podivejte se na http://www.root.cz!")
["http://www.root.cz" "www." ""]
 
user=> (re-find http-regexp "Na http://www.google.com jsem to nenasel.")
["http://www.google.com" "www." ""]

Nyní regulární výraz navázaný na symbol http-regexp využijeme ve funkci, která v řetězci najde URL a nahradí je příslušnou HTML značkou:

(defn create-links
    [string]
    (let [href (re-find http-regexp string)]
        (if href
            (str/replace string http-regexp (str "<a href='" (first href) "'>" (first href) "</a>"))
            string)))
 
user=> (create-links "Podivejte se na http://www.root.cz!")
"Podivejte se na <a href='http://www.root.cz'>http://www.root.cz</a>!"
user=> (create-links "Na http://www.google.com jsem to nenasel.")
"Na <a href='http://www.google.com'>http://www.google.com</a> jsem to nenasel."

9. Funkce pro spojování a rozdělování řetězců

Pro spojení řetězců se používá funkce clojure.string/join, které je možné v prvním parametru předat znak (či řetězec), který se vloží mezi spojované části. Použití této funkce je snadné:

user=> (str/join (range 1 10))
"123456789"
 
user=> (str/join "," (range 1 10))
"1,2,3,4,5,6,7,8,9"

Pro rozdělení řetězce na části lze použít funkci clojure.string/split. V mnoha případech je však snadnější použít clojure.string/split-lines, která řetězec rozdělí na znacích pro nový řádek. To je velmi výhodné, například při načítání konfiguračních či databázových souborů:

user=> (str/split-lines "Hello\nworld")
["Hello" "world"]

Podívejme se na praktický příklad:

(slurp "/etc/passwd")
 
(str/split-lines (slurp "/etc/passwd"))
 
(for [line (str/split-lines (slurp "/etc/passwd"))]
    (first (str/split line #":")))

Výsledek (jména uživatelů) lze setřídit:

widgety

(sort
    (for [line (str/split-lines (slurp "/etc/passwd"))]
        (first (str/split line #":"))))

10. Řetězce chápané jako sekvence znaků

Řetězce lze funkcí seq rozdělit na sekvenci znaků a použít tak všechny funkce, které Clojure pro práci se sekvencemi nabízí (třeba i funkci frequencies):

user=> (seq "hello world!")
(\h \e \l \l \o \space \w \o \r \l \d \!)
 
user=> (-> (concat (range 1 12) (range 3 10) [6]) frequencies)
{7 2, 1 1, 4 2, 6 3, 3 2, 2 1, 11 1, 9 2, 5 2, 10 1, 8 2}
 
user=> (frequencies (seq "hello world!"))
{\space 1, \! 1, \d 1, \e 1, \h 1, \l 3, \o 2, \r 1, \w 1}

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

Stalo se již zvykem uvést odkazy na všechny předchozí části tohoto seriálu. Tento zvyk samozřejmě dodržíme i dnes:

  1. Leiningen: nástroj pro správu projektů napsaných v Clojure
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure/
  2. 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/
  3. 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/
  4. 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/
  5. 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/
  6. 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/
  7. Programovací jazyk Clojure a databáze (1.část)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-databaze-1-cast/
  8. Pluginy pro Leiningen
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-pluginy-pro-leiningen/
  9. 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/
  10. 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/
  11. 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/
  12. 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/
  13. 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/
  14. Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure
    http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure/
  15. 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/
  16. 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/
  17. Programovací jazyk Clojure a práce s Gitem
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem/
  18. Programovací jazyk Clojure a práce s Gitem (2)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem-2/

12. Odkazy na Internetu

  1. Clojure home page
    http://clojure.org/downloads
  2. Clojure – Functional Programming for the JVM
    http://java.ociweb.com/mar­k/clojure/article.html
  3. Clojure quick reference
    http://faustus.webatu.com/clj-quick-ref.html
  4. 4Clojure
    http://www.4clojure.com/
  5. ClojureDoc
    http://clojuredocs.org/
  6. Clojure (Wikipedia EN)
    http://en.wikipedia.org/wiki/Clojure
  7. Clojure (Wikipedia CS)
    http://cs.wikipedia.org/wiki/Clojure
  8. Riastradh's Lisp Style Rules
    http://mumble.net/~campbe­ll/scheme/style.txt
  9. Dynamic Languages Strike Back
    http://steve-yegge.blogspot.cz/2008/05/dynamic-languages-strike-back.html
  10. Scripting: Higher Level Programming for the 21st Century
    http://www.tcl.tk/doc/scripting.html
  11. Java Virtual Machine Support for Non-Java Languages
    http://docs.oracle.com/ja­vase/7/docs/technotes/gui­des/vm/multiple-language-support.html
  12. Třída java.lang.String
    http://docs.oracle.com/ja­vase/7/docs/api/java/lang/Strin­g.html
  13. Třída java.lang.StringBuffer
    http://docs.oracle.com/ja­vase/7/docs/api/java/lang/Strin­gBuffer.html
  14. Třída java.lang.StringBuilder
    http://docs.oracle.com/ja­vase/7/docs/api/java/lang/Strin­gBuilder.html
  15. StringBuffer versus String
    http://www.javaworld.com/ar­ticle/2076072/build-ci-sdlc/stringbuffer-versus-string.html
  16. Threading macro (dokumentace k jazyku Clojure)
    https://clojure.github.io/clo­jure/clojure.core-api.html#clojure.core/->
  17. Understanding the Clojure → macro
    http://blog.fogus.me/2009/09/04/un­derstanding-the-clojure-macro/
  18. clojure.inspector
    http://clojure.github.io/clo­jure/clojure.inspector-api.html
  19. The Clojure Toolbox
    http://www.clojure-toolbox.com/
  20. Unit Testing in Clojure
    http://nakkaya.com/2009/11/18/unit-testing-in-clojure/
  21. Testing in Clojure (Part-1: Unit testing)
    http://blog.knoldus.com/2014/03/22/tes­ting-in-clojure-part-1-unit-testing/
  22. API for clojure.test – Clojure v1.6 (stable)
    https://clojure.github.io/clo­jure/clojure.test-api.html
  23. Leiningen: úvodní stránka
    http://leiningen.org/
  24. Leiningen: Git repository
    https://github.com/techno­mancy/leiningen
  25. leiningen-win-installer
    http://leiningen-win-installer.djpowell.net/
  26. Clojure 1: Úvod
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm/
  27. Clojure 2: Symboly, kolekce atd.
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-2-cast/
  28. 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/
  29. 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/
  30. 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/
  31. Clojure 6: Podpora pro paralelní programování
    http://www.root.cz/clanky/programovaci-jazyk-clojure-6-futures-nejsou-jen-financni-derivaty/
  32. Clojure 7: Další funkce pro paralelní programování
    http://www.root.cz/clanky/programovaci-jazyk-clojure-7-dalsi-podpurne-prostredky-pro-paralelni-programovani/
  33. 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/
  34. 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/
  35. Clojure 10: Kooperace mezi Clojure a Javou
    http://www.root.cz/clanky/programovaci-jazyk-clojure-10-kooperace-mezi-clojure-a-javou-pokracovani/
  36. Clojure 11: Generátorová notace seznamu/list comprehension
    http://www.root.cz/clanky/programovaci-jazyk-clojure-11-generatorova-notace-seznamu-list-comprehension/
  37. 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/
  38. 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/
  39. Clojure 14: Základy práce se systémem maker
    http://www.root.cz/clanky/programovaci-jazyk-clojure-14-zaklady-prace-se-systemem-maker/
  40. Clojure 15: Tvorba uživatelských maker
    http://www.root.cz/clanky/programovaci-jazyk-clojure-15-tvorba-uzivatelskych-maker/
  41. Clojure 16: Složitější uživatelská makra
    http://www.root.cz/clanky/programovaci-jazyk-clojure-16-slozitejsi-uzivatelska-makra/
  42. Clojure 17: Využití standardních maker v praxi
    http://www.root.cz/clanky/programovaci-jazyk-clojure-17-vyuziti-standardnich-maker-v-praxi/
  43. Clojure 18: Základní techniky optimalizace aplikací
    http://www.root.cz/clanky/programovaci-jazyk-clojure-18-zakladni-techniky-optimalizace-aplikaci/
  44. Clojure 19: Vývojová prostředí pro Clojure
    http://www.root.cz/clanky/programovaci-jazyk-clojure-19-vyvojova-prostredi-pro-clojure/
  45. 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/
  46. Clojure 21: ClojureScript aneb překlad Clojure do JS
    http://www.root.cz/clanky/programovaci-jazyk-clojure-21-clojurescript-aneb-preklad-clojure-do-javascriptu/
Našli jste v článku chybu?
Podnikatel.cz: Takhle se prodávají mražené potraviny

Takhle se prodávají mražené potraviny

DigiZone.cz: Parlamentní listy: kde končí PR...

Parlamentní listy: kde končí PR...

Vitalia.cz: Opuncie je plod kaktusu. Pozor na trny

Opuncie je plod kaktusu. Pozor na trny

Vitalia.cz: dTest odhalil ten nejlepší kečup

dTest odhalil ten nejlepší kečup

Vitalia.cz: Tahák, jak vyzrát nad zápachem z úst

Tahák, jak vyzrát nad zápachem z úst

DigiZone.cz: Numan Two: rozhlasový přijímač s CD

Numan Two: rozhlasový přijímač s CD

Podnikatel.cz: Znáte už 5 novinek k #EET

Znáte už 5 novinek k #EET

Lupa.cz: Aukro.cz mění majitele. Vrací se do českých rukou

Aukro.cz mění majitele. Vrací se do českých rukou

DigiZone.cz: Wimbledon na Nova Sport až do 2019

Wimbledon na Nova Sport až do 2019

Vitalia.cz: Kterou dýni můžete jíst za syrova?

Kterou dýni můžete jíst za syrova?

Lupa.cz: Patička e-mailu závazná jako vlastnoruční podpis?

Patička e-mailu závazná jako vlastnoruční podpis?

Vitalia.cz: Jsou vegani a vyrábějí nemléko

Jsou vegani a vyrábějí nemléko

DigiZone.cz: Funbox 4K v DVB-T2 má ostrý provoz

Funbox 4K v DVB-T2 má ostrý provoz

DigiZone.cz: Nova opět stahuje „milionáře“

Nova opět stahuje „milionáře“

Vitalia.cz: Test dětských svačinek: Tyhle ne!

Test dětských svačinek: Tyhle ne!

DigiZone.cz: Rapl: seriál, který vás smíří s ČT

Rapl: seriál, který vás smíří s ČT

Podnikatel.cz: Babišovi se nedá věřit, stěžovali si hospodští

Babišovi se nedá věřit, stěžovali si hospodští

120na80.cz: Hrbatá prsa aneb mýty o implantátech

Hrbatá prsa aneb mýty o implantátech

Vitalia.cz: Tohle jsou nejlepší česká piva podle odborníků

Tohle jsou nejlepší česká piva podle odborníků

Podnikatel.cz: Chystá se smršť legislativních novinek

Chystá se smršť legislativních novinek