Obsah
1. Vytváříme IRC bota v programovacím jazyce Clojure
3. Připojení IRC bota k serveru
4. Příjem zprávy a generování odpovědi
5. Demonstrační příklad ircbot1: první jednoduchá varianta IRC bota
6. Rozhodnutí, zda zpráva přišla z kanálu nebo v soukromém chatu a zda jde o zprávu pro bota
7. Druhý demonstrační příklad ircbot2: odpovídání na zprávy určené pouze botovi
8. Rozpoznání dotazu, parsing čísla, výpočet faktoriálu
9. Třetí demonstrační příklad ircbot3: výpočet faktoriálu (smysluplná odpověď)
10. Odpověď tazateli na kanálu a přímá odpověď v soukromém chatu
11. Čtvrtý demonstrační příklad ircbot4: rozpoznání jména bota, odpověď přímo tazateli atd.
12. Repositář s demonstračními příklady
13. Odkazy na předchozí části tohoto seriálu
1. Vytváříme IRC bota v programovacím jazyce Clojure
V seriálu o programovacím jazyce Clojure jsme se již seznámili s mnoha knihovnami, které je možné použít jak pro tvorbu serverových aplikací, tak i pro aplikace provozované na desktopech či na vývojářských strojích. Připomeňme si jen ve stručnosti, že jsme se zabývali popisem knihoven Clisk (generování procedurální grafiky), Enlive (šablonovací systém pro HTML a XML), Codox (generování dokumentace ke zdrojovým kódům projektů), Cloverage (zjištění pokrytí programového kódu jednotkovými testy), Clj-Git (rozhraní ke GITu), core.matrix (efektivní práce s vektory a maticemi), Seesaw (funkcionálně pojaté rozhraní ke Swingu), Clojure Ring (webový server, opět s funkcionálními rysy), Hiccup (překladač z Clojure do HTML) a taktéž s knihovnou core.async (podpora pro asynchronní programování). Dnes si řekneme základní informace o užitečné knihovně irclj, kterou lze použít pro tvorbu takzvaných IRC botů.
IRC neboli Internet Relay Chat je jednou z prvních Internetových služeb sloužících pro komunikaci v reálném čase (jinými slovy pro chatování). IRC vznikl již v roce 1988, a to konkrétně ve Finském akademickém prostředí. I přes vznik různých dalších podobně zaměřených služeb (asi nejznámější je XMPP/Jabber, dříve ICQ apod.) se IRC stále používá a populární je zejména mezi vývojáři. Jedním z fenoménů, které službu IRC provázejí už prakticky od začátku, jsou takzvaní „boti“, což jsou programy, které mohou komunikovat s uživateli (nebo i s jinými boty) pomocí textových zpráv. Implementace botů není příliš složitá, protože i samotný protokol používaný službou IRC je poměrně jednoduchý (posílat zprávy lze i přes pouhý netcat). Boti se používají jak pro zábavu, tak i pro vážnější služby; ostatně na kanálu #clojure na serveru irc.freenode.net nalezneme bota, který dokáže interpretovat výrazy zapsané v Clojure, odkazovat na dokumentaci či trousit poznámky o některých ne zcela podařených programátorských obratech (tento kanál mimochodem navštěvuje hodně známých programátorů z komunity nejenom okolo jazyka Clojure).
2. Knihovna irclj
IRC boty je možné naprogramovat prakticky v jakémkoli programovacím jazyce. Přitom je zajímavé, že mnoho botů bylo vytvořeno v Perlu, a to pravděpodobně z toho důvodu, že Perl byl na konci minulého tisíciletí ve vývojářské komunitě populární a navíc poskytoval a samozřejmě dodnes poskytuje prostředky pro snadnou manipulaci s textem, tj. i s textovými zprávami, které jsou přes IRC přenášeny. My se však v tomto článku samozřejmě zaměříme na programovací jazyk Clojure, pro nějž již vzniklo hned několik knihoven pro práci s IRC. Knihovna nazvaná irclj, kterou dnes použijeme, je z praktického pohledu zajímavá tím, že je velmi snadno použitelná (podobně jako již popsaná knihovna Clojure Ring) a navíc je možné případného IRC bota rozšiřovat o další funkce. Celý bot může být naprogramován funkcionálním stylem, což zde konkrétně znamená, že v jedné asynchronně volané callback funkci dochází k transformaci vstupní zprávy na zprávu výstupní.
Přidání knihovny irclj do vlastního projektu je velmi snadné, což ostatně ukazuje i následující projektový soubor:
(defproject ircbot1 "0.1.0-SNAPSHOT" :description "FIXME: write description" :url "http://example.com/FIXME" :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} :dependencies [[org.clojure/clojure "1.7.0"] [irclj "0.5.0-alpha4"]] :main ^:skip-aot ircbot1.core :target-path "target/%s" :profiles {:uberjar {:aot :all}})
Poznámka: podobný soubor, pouze s odlišným jménem projektu a hlavního modulu, je použit ve všech čtyřech demonstračních příkladech, takže si kopii tohoto souboru již nebudeme ukazovat.
Poznámka2: označení „0.5.0-alpha4“ se není třeba bát, protože knihovna irclj v alfa verzi je ve skutečnosti v mnohem lepším stavu, než mnohé knihovny a frameworky ve své stabilní verzi :-)
3. Připojení IRC bota k serveru, příjem zprávy a generování odpovědi
Použití knihovny irclj je poměrně jednoduché. Podívejme se nejprve na kroky, které je zapotřebí provést při implementaci vlastního IRC bota:
- Navázání spojení s vybraným IRC serverem a registrace nicku (přezdívky) bota.
- Připojení k vybranému kanálu či kanálům, na nichž má být bot dostupný (nepovinné, protože s botem je možné komunikovat i s využitím pouze soukromých zpráv).
- Realizace callback funkce zavolané ve chvíli, kdy je botovi poslána zpráva.
První krok, tj. připojení k IRC serveru, se realizuje s využitím funkce irclj.core/connect, které se předá jméno serveru, port, na který se má bot připojit, nick (přezdívka) bota, a případné další parametry:
(irclj.core/connect jméno_serveru port nick další_parametry)
Konkrétně může být připojení navázáno tímto způsobem. Posledním parametrem je specifikována uživatelská callback funkce volaná při přijetí zprávy:
(irclj.core/connect "irc.freenode.net" 6667 "unique" :callbacks {:privmsg callback-function})
Druhým krokem je připojení bota k vybranému kanálu či kanálům, což zajišťuje funkce nazvaná irclj.core/join. První parametrem této funkce je hodnota reprezentující navázané spojení, tj. hodnota vrácená již popsanou funkcí irclj.core/connect:
(irclj.core/join connection jmeno-kanalu)
Konkrétní způsob použití této funkce:
(irclj.core/join connection "#botwar")
Pro zjednodušení konfigurace budeme v demonstračních příkladech používat datovou strukturu (konkrétně mapu) nazvanou configuration, v níž budou uloženy všechny potřebné parametry připojení:
(def configuration "Datova struktura obsahujici informace o pripojeni na IRC server." {:server "irc.freenode.net" ; vsem dostupny IRC server :port 6667 ; vychozi port :channel "#botwar" ; kanal, ktery je urceny pro testovani botu ; (dostanete zde mj. prava channel operatora) :nick "cljbot42"}) ; tuto cast bude potreba zmenit, nehadejte se ; prosim o stejny nick :)
Navázání připojení potom zajišťuje uživatelská funkce pojmenovaná start-irc-bot. Nejprve se zavolá irclj.core/connect, výsledek a její návratová hodnota se použije pro irclj.core/join:
(defn start-irc-bot "Funkce, ktera zajisti spusteni IRC bota a jeho pripojeni na zvoleny server a kanal(y)." [configuration callback-function] (println "Connecting to" (:server configuration) "port" (:port configuration)) ; vlastni pripojeni na server (let [connection (irclj.core/connect (:server configuration) (:port configuration) (:nick configuration) :callbacks {:privmsg callback-function})] (println "Connected, joining to channel" (:channel configuration)) ; pripojeni ke zvolenemu kanalu ci kanalum (irclj.core/join connection (:channel configuration)) ; snad je vse ok :) (println "Connected...")))
Obrázek 1: Připojení bota do kanálu a začátek konverzace.
4. Příjem zprávy a generování odpovědi
Do navázání spojení a připojení bota ke zvolenému kanálu je nutné nějakým způsobem odpovídat na přijímané zprávy. K tomu slouží uživatelská callback funkce, kterou jsme pojmenovali on-incoming-message a zaregistrovali při volání irclj.core/connect. Nejprve se podívejme, jak tato funkce vypadá:
(defn on-incoming-message "Callback funkce volana pri prichodu zpravy na kanal ci na soukromy chat." [connection incoming-message] ; rozdeleni zpravy na jednotlive prvky (let [{text :text target :target nick :nick host :host command :command} incoming-message] ; zalogujeme, co se prave stalo (println "Received message from" nick "to" target ":" text "(" host command ")") ; a vytvorime vhodnou odpoved (irclj.core/reply connection (create-reply incoming-message) (str "hello " nick))))
Tato funkce je při příchodu zprávy zavolána a jsou jí předány dva parametry. V prvním parametru je uložena hodnota s informacemi o připojení, tj. hodnota, kterou vrací funkce irclj.core/connect. Tuto hodnotu použijeme při vytváření odpovědi. Druhým automaticky naplňovaným parametrem je datová struktura obsahující samotnou zprávu. Jak je v programovacím jazyku Clojure dobrým zvykem, je tato datová struktura reprezentována mapou, kde klíči jsou takzvané keywords, tj. unikátní hodnoty. My nejprve ze zprávy získáme pětici prvků a uložíme je do lokálních proměnných pojmenovaných text, target, nick, host a command (používá se zde postup nazývaný „destructuring“ a navíc fakt, že i samotný keyword se chová jako funkce get). Následně jsou na standardní výstup vypsány informace o přijetí zprávy:
Obrázek 2: Zprávy vypisované botem na standardní výstup.
O odpověď se postará irclj.core/reply, které se předá mapa se stejnou strukturou, jako má příchozí zpráva, ale samozřejmě s jiným obsahem. To je řešeno v uživatelské funkci nazvané příhodně create-reply. Tato funkce se rozhodne, zda jí přichází soukromá zpráva či zpráva na kanálu a potom buď přímo odpoví nebo pošle zprávu zpět přímo uživateli (target bude nastaven na přezdívku uživatele a nikoli na jméno kanálu; náhradu zařizuje funkce assoc vracející upravenou mapu):
(defn create-reply "Vytvoreni datove struktury popisujici odpoved." [incoming-message] ; rozhodnuti, zda se ma odpoved poslat zpet do kanalu nebo na soukromy chat (if (.startsWith (:target incoming-message) "#") ; cilem je jmeno kanalu incoming-message ; -> posleme odpoved zpet na kanal ; v opacnem pripade posleme zpravu primo uzivateli ; cilem bude prezdivka uzivatele (assoc incoming-message :target (:nick incoming-message))))
Obrázek 3: Zpráva zaslaná botovi soukromě. Bot správně odpoví na soukromý chat a nikoli do kanálu.
5. Demonstrační příklad ircbot1: první jednoduchá varianta IRC bota
Postupy a uživatelské funkce vysvětlené v předchozích dvou kapitolách jsou použity v dnešním prvním demonstračním příkladu pojmenovaném jednoduše ircbot1. Celý projekt s tímto demonstračním příkladem naleznete na adrese https://github.com/tisnik/clojure-examples/tree/master/ircbot1. Po spuštění tohoto příkladu se provede připojení ke známému IRC serveru „irc.freenode.net“ přes port 6667. Bot se bude snažit získat přezdívku „cljbot42“, což se pravděpodobně nepovede v případě, že si tento příklad spustí více čtenářů současně, takže si prosím přezdívku ve zdrojovém kódu změňte (třeba si do ni přidejte hostname atd.). Následně se bot připojí na kanál #botwar a bude zde očekávat zprávy, přičemž o zpracování zpráv se podělí dvě uživatelské funkce pojmenované on-incomming-message a create-reply:
; ; Prvni varianta velmi jednoducheho IRC bota ; ; Autor: Pavel Tisnovsky ; (ns ircbot1.core (:gen-class)) (require '[irclj.core :as irc]) (def configuration "Datova struktura obsahujici informace o pripojeni na IRC server." {:server "irc.freenode.net" ; vsem dostupny IRC server :port 6667 ; vychozi port :channel "#botwar" ; kanal, ktery je urceny pro testovani botu ; (dostanete zde mj. prava channel operatora) :nick "cljbot42"}) ; tuto cast bude potreba zmenit, nehadejte se ; prosim o stejny nick :) (defn create-reply "Vytvoreni datove struktury popisujici odpoved." [incoming-message] ; rozhodnuti, zda se ma odpoved poslat zpet do kanalu nebo na soukromy chat (if (.startsWith (:target incoming-message) "#") ; cilem je jmeno kanalu incoming-message ; -> posleme odpoved zpet na kanal ; v opacnem pripade posleme zpravu primo uzivateli ; cilem bude prezdivka uzivatele (assoc incoming-message :target (:nick incoming-message)))) (defn on-incoming-message "Callback funkce volana pri prichodu zpravy na kanal ci na soukromy chat." [connection incoming-message] ; rozdeleni zpravy na jednotlive prvky (let [{text :text target :target nick :nick host :host command :command} incoming-message] ; zalogujeme, co se prave stalo (println "Received message from" nick "to" target ":" text "(" host command ")") ; a vytvorime vhodnou odpoved (irc/reply connection (create-reply incoming-message) (str "hello " nick)))) (defn start-irc-bot "Funkce, ktera zajisti spusteni IRC bota a jeho pripojeni na zvoleny server a kanal(y)." [configuration callback-function] (println "Connecting to" (:server configuration) "port" (:port configuration)) ; vlastni pripojeni na server (let [connection (irc/connect (:server configuration) (:port configuration) (:nick configuration) :callbacks {:privmsg callback-function})] (println "Connected, joining to channel" (:channel configuration)) ; pripojeni ke zvolenemu kanalu ci kanalum (irc/join connection (:channel configuration)) ; snad je vse ok :) (println "Connected..."))) (defn -main "Vstupni bod do teto aplikace." [& args] (start-irc-bot configuration on-incoming-message))
Obrázek 4: Na kanálu #botwar dostanou všichni práva operátora, což je pro ladění botů výhodné – je možné je ihned vyhodit :-)
6. Rozhodnutí, zda zpráva přišla z kanálu nebo v soukromém chatu a zda jde o zprávu pro bota
Už v první verzi IRC bota bylo nutné rozhodnout, zda zpráva, na kterou bot reaguje (nebo také nereaguje) přišla na kanál nebo přes soukromý chat. Tento test je vlastně velmi jednoduchý, protože stačí ověřit, jestli cíl (target) zprávy začíná znakem #. Nově tedy test implementujeme ve vlastní funkci – predikátu (proto název této funkce končí na otazník):
(defn message-to-channel? "Test, zda se jedna o zpravu poslanou na kanal." [message] (.startsWith (:target message) "#"))
Funkce pro vytvoření odpovědi se poněkud změní:
(defn create-reply "Vytvoreni datove struktury popisujici odpoved." [incoming-message] ; rozhodnuti, zda se ma odpoved poslat zpet do kanalu nebo na soukromy chat (if (message-to-channel? incoming-message) ; pokud je cilem primo jmeno kanalu incoming-message ; -> posleme odpoved zpet na kanal ; v opacnem pripade posleme zpravu primo uzivateli ; cilem bude prezdivka uzivatele (assoc incoming-message :target (:nick incoming-message))))
Obrázek 5: Původní verze bota reagovala na všechny zprávy.
Další test, který je nutné implementovat, ověří, zda je zpráva poslána přímo botovi. Zde jsou dvě možnosti: u privátní zprávy stačí otestovat jejího příjemce (target), zatímco u zprávy poslané na kanál se zjistí, zda zpráva začíná správným nickem. Poněkud naivní implementace tohoto druhého testu může vypadat následovně:
(defn message-for-me? "Vrati logickou 'pravdu' v pripade, ze se byla prijata prima zprava ci privatni zprava." [my-name message] (or (.startsWith (:target message) my-name) ; privatni zprava (.startsWith (:text message) (str my-name ":")); prima zprava ))
Nyní může bot reagovat pouze na zprávu určenou přímo jemu, což zajišťuje přidaná podmínka:
(defn on-incoming-message "Callback funkce volana pri prichodu zpravy na kanal ci na soukromy chat." [connection incoming-message] ; rozdeleni zpravy na jednotlive prvky (let [{text :text target :target nick :nick host :host command :command} incoming-message] ; zalogujeme, co se prave stalo (println "Received message from" nick "to" target ":" text "(" host command ")") ; test, zda je zprava skutecne urcena pro bota (if (message-for-me? (:nick configuration) incoming-message) ; vytvorime vhodnou odpoved (irc/reply connection (create-reply incoming-message) (str "hello " nick)))))
Obrázek 6: Nová verze bota reaguje jen na zprávy poslané přímo jemu.
7. Druhý demonstrační příklad ircbot2: odpovídání na zprávy určené pouze botovi
Podobně, jako tomu bylo už v prvním demonstračním příkladu, i druhý příklad využívá funkce popsané v předchozí kapitole. Tento příklad se jmenuje ircbot2 a jeho zdrojový kód naleznete na adrese https://github.com/tisnik/clojure-examples/tree/master/ircbot2. Při testování bota prosím nezapomeňte změnit jeho nick popř. ho otestovat na jiném serveru, než je „irc.freenode.net“:
; ; Druha varianta velmi jednoducheho IRC bota: ; bot nyni odpovida pouze na zpravy, ktere jsou mu urceny ; ; Autor: Pavel Tisnovsky ; (ns ircbot2.core (:gen-class)) (require '[irclj.core :as irc]) (def configuration "Datova struktura obsahujici informace o pripojeni na IRC server." {:server "irc.freenode.net" ; vsem dostupny IRC server :port 6667 ; vychozi port :channel "#botwar" ; kanal, ktery je urceny pro testovani botu ; (dostanete zde mj. prava channel operatora) :nick "cljbot42"}) ; tuto cast bude potreba zmenit, nehadejte se ; prosim o stejny nick :) (defn message-to-channel? "Test, zda se jedna o zpravu poslanou na kanal." [message] (.startsWith (:target message) "#")) (defn message-for-me? "Vrati logickou 'pravdu' v pripade, ze se byla prijata prima zprava ci privatni zprava." [my-name message] (or (.startsWith (:target message) my-name) ; privatni zprava (.startsWith (:text message) (str my-name ":")); prima zprava )) (defn create-reply "Vytvoreni datove struktury popisujici odpoved." [incoming-message] ; rozhodnuti, zda se ma odpoved poslat zpet do kanalu nebo na soukromy chat (if (message-to-channel? incoming-message) ; pokud je cilem primo jmeno kanalu incoming-message ; -> posleme odpoved zpet na kanal ; v opacnem pripade posleme zpravu primo uzivateli ; cilem bude prezdivka uzivatele (assoc incoming-message :target (:nick incoming-message)))) (defn on-incoming-message "Callback funkce volana pri prichodu zpravy na kanal ci na soukromy chat." [connection incoming-message] ; rozdeleni zpravy na jednotlive prvky (let [{text :text target :target nick :nick host :host command :command} incoming-message] ; zalogujeme, co se prave stalo (println "Received message from" nick "to" target ":" text "(" host command ")") ; test, zda je zprava skutecne urcena pro bota (if (message-for-me? (:nick configuration) incoming-message) ; vytvorime vhodnou odpoved (irc/reply connection (create-reply incoming-message) (str "hello " nick))))) (defn start-irc-bot "Funkce, ktera zajisti spusteni IRC bota a jeho pripojeni na zvoleny server a kanal(y)." [configuration callback-function] (println "Connecting to" (:server configuration) "port" (:port configuration)) ; vlastni pripojeni na server (let [connection (irc/connect (:server configuration) (:port configuration) (:nick configuration) :callbacks {:privmsg callback-function})] (println "Connected, joining to channel" (:channel configuration)) ; pripojeni ke zvolenemu kanalu ci kanalum (irc/join connection (:channel configuration)) ; snad je vse ok :) (println "Connected..."))) (defn -main "Vstupni bod do teto aplikace." [& args] (start-irc-bot configuration on-incoming-message))
8. Rozpoznání dotazu, parsing čísla, výpočet faktoriálu
Aby byl IRC bot nějakým způsobem užitečný nebo zábavný, musí reagovat na zprávy, které mu byly zaslány. My si ukážeme velmi jednoduchou variantu bota, která prozatím pouze vypočítá faktoriál z přirozeného čísla (což sice není příliš užitečné ani zábavné, ale nějak se začít musí). Funkce pro výpočet faktoriálu může v podání jazyka Clojure používat apply (rekurzí se zde nechte otravovat pouze ve škole, zde pro výpočet faktoriálu se nehodí :-) a operaci (což je taktéž funkce) *'. Apostrof zde mění význam násobení, protože automaticky rozpozná přetečení čísel typu int či long a přejde na výpočty s čísly reprezentovanými objekty typu BigDecimal:
(defn factorial "Vypocet faktorialu, varianta pracujici korektne s typem BigDecimal." [n] (if (neg? n) "negative numbers are not supported!" (apply *' (range 1 (inc n)))))
Obrázek 7: IRC bot dokáže spočítat faktoriál prakticky jakéhokoli přirozeného čísla.
Dále si připravíme funkci pro převod textu na číslo typu BigDecimal (šlo by ovšem použít i int). Pokud zpráva neobsahuje číslo nebo došlo k nějaké jiné chybě, vrací se nil:
(defn text->number "Prevod parametru specifikovaneho v param-name na cislo typu BigDecimal." [input-text] (try (let [number (re-find #"\d+" input-text)] (if number (bigdec number))) ; pokus o prevod na BigDecimal (catch Exception e nil))) ; pokud se prevod nepovede, vraci se nil
Samotná reakce na zprávu poslanou uživatelem přímo botovi by mohla vypadat následovně (povšimněte si, že pokud se číslo ve zprávě nenalezne, odpoví bot otazníkem; sami si namísto něj můžete přidat jinou vtipnou odpověď):
(defn prepare-reply-text "Priprava odpovedi." [input-text] (let [number (text->number input-text)] ; pokud se podarilo precist cislo (if number ; vypocte a vrati se jeho faktorial (factorial number) "?")))
Obrázek 8: Pokud se číslo nerozpozná, vypíše se pouze otazník.
9. Třetí demonstrační příklad ircbot3: výpočet faktoriálu (smysluplná odpověď)
Podívejme se nyní, jak budou výše popsané funkce factorial, text->number a prepare-reply-text použity ve třetí variantě jednoduchého IRC bota. Základní struktura bota se nijak neliší od předchozích dvou variant (připojení k serveru, registrace nicku, připojení ke kanálu), odlišné je pouze naprogramování jeho reakce na příchozí zprávy:
; ; Treti varianta velmi jednoducheho IRC bota: ; bot nyni odpovida pouze na zpravy, ktere jsou mu urceny ; pokud rozpozna cislo, vypocte z neho faktorial ; ; Autor: Pavel Tisnovsky ; (ns ircbot3.core (:gen-class)) (require '[irclj.core :as irc]) (def configuration "Datova struktura obsahujici informace o pripojeni na IRC server." {:server "irc.freenode.net" ; vsem dostupny IRC server :port 6667 ; vychozi port :channel "#botwar" ; kanal, ktery je urceny pro testovani botu ; (dostanete zde mj. prava channel operatora) :nick "cljbot42"}) ; tuto cast bude potreba zmenit, nehadejte se ; prosim o stejny nick :) (defn message-to-channel? "Test, zda se jedna o zpravu poslanou na kanal." [message] (.startsWith (:target message) "#")) (defn message-for-me? "Vrati logickou 'pravdu' v pripade, ze se byla prijata prima zprava ci privatni zprava." [my-name message] (or (.startsWith (:target message) my-name) ; privatni zprava (.startsWith (:text message) (str my-name ":")); prima zprava )) (defn create-reply "Vytvoreni datove struktury popisujici odpoved." [incoming-message] ; rozhodnuti, zda se ma odpoved poslat zpet do kanalu nebo na soukromy chat (if (message-to-channel? incoming-message) ; pokud je cilem primo jmeno kanalu incoming-message ; -> posleme odpoved zpet na kanal ; v opacnem pripade posleme zpravu primo uzivateli ; cilem bude prezdivka uzivatele (assoc incoming-message :target (:nick incoming-message)))) (defn text->number "Prevod parametru specifikovaneho v param-name na cislo typu BigDecimal." [input-text] (try (let [number (re-find #"\d+" input-text)] (if number (bigdec number))) ; pokus o prevod na BigDecimal (catch Exception e nil))) ; pokud se prevod nepovede, vraci se nil (defn factorial "Vypocet faktorialu, varianta pracujici korektne s typem BigDecimal." [n] (if (neg? n) "negative numbers are not supported!" (apply *' (range 1 (inc n))))) (defn prepare-reply-text "Priprava odpovedi." [input-text] (let [number (text->number input-text)] ; pokud se podarilo precist cislo (if number ; vypocte a vrati se jeho faktorial (factorial number) "?"))) (defn on-incoming-message "Callback funkce volana pri prichodu zpravy na kanal ci na soukromy chat." [connection incoming-message] ; rozdeleni zpravy na jednotlive prvky (let [{text :text target :target nick :nick host :host command :command} incoming-message] ; zalogujeme, co se prave stalo (println "Received message from" nick "to" target ":" text "(" host command ")") ; test, zda je zprava skutecne urcena pro bota (if (message-for-me? (:nick configuration) incoming-message) ; vytvorime vhodnou odpoved (irc/reply connection (create-reply incoming-message) (prepare-reply-text text))))) (defn start-irc-bot "Funkce, ktera zajisti spusteni IRC bota a jeho pripojeni na zvoleny server a kanal(y)." [configuration callback-function] (println "Connecting to" (:server configuration) "port" (:port configuration)) ; vlastni pripojeni na server (let [connection (irc/connect (:server configuration) (:port configuration) (:nick configuration) :callbacks {:privmsg callback-function})] (println "Connected, joining to channel" (:channel configuration)) ; pripojeni ke zvolenemu kanalu ci kanalum (irc/join connection (:channel configuration)) ; snad je vse ok :) (println "Connected..."))) (defn -main "Vstupni bod do teto aplikace." [& args] (start-irc-bot configuration on-incoming-message))
10. Odpověď tazateli na kanálu a přímá odpověď v soukromém chatu
Všimli jste si, jakým způsobem náš IRC bot odpoví ve chvíli, kdy se ho zeptáme na kanále (nikoli v privátní zprávě)? Podívejte se na následující screenshot:
Obrázek 9: Všimli jste si, co nyní IRC bot počítá?
Problém je jednoduchý – samotný nick bota už obsahuje číslo (42), které je „správně“ rozpoznáno a je vypočítán jeho faktoriál. Pro opravu tohoto problému je nutné nepatrně změnit uživatelskou funkci prepare-reply-text tak, aby se u zpráv posílaných do kanálu odstranilo jméno bota:
; test, zda zpráva přišla do kanálu in-channel? (message-to-channel? incomming-message) ; získání vstupního čísla n number (text->number (if in-channel? (subs input-text (count (:nick configuration))) input-text))
Nový tvar funkce prepare-reply-text může vypadat následovně:
(defn prepare-reply-text "Priprava odpovedi." [incomming-message nick input-text] (let [in-channel? (message-to-channel? incomming-message) number (text->number (if in-channel? (subs input-text (count (:nick configuration))) input-text)) prefix (if in-channel? (str nick ": "))] ; pokud se podarilo precist cislo (if number ; vypocte a vrati se jeho faktorial + odpoved uzivateli (str prefix (factorial number)) (str prefix "?"))))
Obrázek 10: První úspěch u uživatelů :-) + nová verze IRC bota (druhá část zpráv).
11. Čtvrtý demonstrační příklad ircbot4: rozpoznání jména bota, odpověď přímo tazateli atd.
Na závěr si ukažme úplný zdrojový kód čtvrté verze IRC bota, který by již měl správně počítat faktoriály i ve chvíli, kdy samotný nick obsahuje číslo:
; ; Ctvrta varianta velmi jednoducheho IRC bota: ; bot nyni odpovida pouze na zpravy, ktere jsou mu urceny ; pokud rozpozna cislo, vypocte z neho faktorial, spravne ; ignoruje sve jmeno, odpovida primo tazateli atd. ; ; Autor: Pavel Tisnovsky ; (ns ircbot4.core (:gen-class)) (require '[irclj.core :as irc]) (def configuration "Datova struktura obsahujici informace o pripojeni na IRC server." {:server "irc.freenode.net" ; vsem dostupny IRC server :port 6667 ; vychozi port :channel "#botwar" ; kanal, ktery je urceny pro testovani botu ; (dostanete zde mj. prava channel operatora) :nick "cljbot42"}) ; tuto cast bude potreba zmenit, nehadejte se ; prosim o stejny nick :) (defn message-to-channel? "Test, zda se jedna o zpravu poslanou na kanal." [message] (.startsWith (:target message) "#")) (defn message-for-me? "Vrati logickou 'pravdu' v pripade, ze se byla prijata prima zprava ci privatni zprava." [my-name message] (or (.startsWith (:target message) my-name) ; privatni zprava (.startsWith (:text message) (str my-name ":")); prima zprava )) (defn create-reply "Vytvoreni datove struktury popisujici odpoved." [incoming-message] ; rozhodnuti, zda se ma odpoved poslat zpet do kanalu nebo na soukromy chat (if (message-to-channel? incoming-message) ; pokud je cilem primo jmeno kanalu incoming-message ; -> posleme odpoved zpet na kanal ; v opacnem pripade posleme zpravu primo uzivateli ; cilem bude prezdivka uzivatele (assoc incoming-message :target (:nick incoming-message)))) (defn text->number "Prevod parametru specifikovaneho v param-name na cislo typu BigDecimal." [input-text] (try (let [number (re-find #"\d+" input-text)] (if number (bigdec number))) ; pokus o prevod na BigDecimal (catch Exception e nil))) ; pokud se prevod nepovede, vraci se nil (defn factorial "Vypocet faktorialu, varianta pracujici korektne s typem BigDecimal." [n] (if (neg? n) "negative numbers are not supported!" (apply *' (range 1 (inc n))))) (defn prepare-reply-text "Priprava odpovedi." [incomming-message nick input-text] (let [in-channel? (message-to-channel? incomming-message) number (text->number (if in-channel? (subs input-text (count (:nick configuration))) input-text)) prefix (if in-channel? (str nick ": "))] ; pokud se podarilo precist cislo (if number ; vypocte a vrati se jeho faktorial + odpoved uzivateli (str prefix (factorial number)) (str prefix "?")))) (defn on-incoming-message "Callback funkce volana pri prichodu zpravy na kanal ci na soukromy chat." [connection incoming-message] ; rozdeleni zpravy na jednotlive prvky (let [{text :text target :target nick :nick host :host command :command} incoming-message] ; zalogujeme, co se prave stalo (println "Received message from" nick "to" target ":" text "(" host command ")") ; test, zda je zprava skutecne urcena pro bota (if (message-for-me? (:nick configuration) incoming-message) ; vytvorime vhodnou odpoved (irc/reply connection (create-reply incoming-message) (prepare-reply-text incoming-message nick text))))) (defn start-irc-bot "Funkce, ktera zajisti spusteni IRC bota a jeho pripojeni na zvoleny server a kanal(y)." [configuration callback-function] (println "Connecting to" (:server configuration) "port" (:port configuration)) ; vlastni pripojeni na server (let [connection (irc/connect (:server configuration) (:port configuration) (:nick configuration) :callbacks {:privmsg callback-function})] (println "Connected, joining to channel" (:channel configuration)) ; pripojeni ke zvolenemu kanalu ci kanalum (irc/join connection (:channel configuration)) ; snad je vse ok :) (println "Connected..."))) (defn -main "Vstupni bod do teto aplikace." [& args] (start-irc-bot configuration on-incoming-message))
12. Repositář s demonstračními příklady
Všechny čtyři demonstrační příklady, které jsme si v dnešním článku popsali, byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/clojure-examples. V tabulce zobrazené pod tímto odstavcem naleznete na zdrojové kódy všech čtyř demonstračních příkladů přímé odkazy:
# | Příklad/knihovna | Github |
---|---|---|
1 | ircbot1 | https://github.com/tisnik/clojure-examples/tree/master/ircbot1 |
2 | ircbot2 | https://github.com/tisnik/clojure-examples/tree/master/ircbot2 |
3 | ircbot3 | https://github.com/tisnik/clojure-examples/tree/master/ircbot3 |
4 | ircbot4 | https://github.com/tisnik/clojure-examples/tree/master/ircbot4 |
13. Odkazy na předchozí části tohoto seriálu
- Clojure 1: Úvod
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm/ - Clojure 2: Symboly, kolekce atd.
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-2-cast/ - 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/ - 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/ - 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/ - Clojure 6: Podpora pro paralelní programování
http://www.root.cz/clanky/programovaci-jazyk-clojure-6-futures-nejsou-jen-financni-derivaty/ - Clojure 7: Další funkce pro paralelní programování
http://www.root.cz/clanky/programovaci-jazyk-clojure-7-dalsi-podpurne-prostredky-pro-paralelni-programovani/ - 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/ - 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/ - Clojure 10: Kooperace mezi Clojure a Javou
http://www.root.cz/clanky/programovaci-jazyk-clojure-10-kooperace-mezi-clojure-a-javou-pokracovani/ - Clojure 11: Generátorová notace seznamu/list comprehension
http://www.root.cz/clanky/programovaci-jazyk-clojure-11-generatorova-notace-seznamu-list-comprehension/ - 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/ - 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/ - Clojure 14: Základy práce se systémem maker
http://www.root.cz/clanky/programovaci-jazyk-clojure-14-zaklady-prace-se-systemem-maker/ - Clojure 15: Tvorba uživatelských maker
http://www.root.cz/clanky/programovaci-jazyk-clojure-15-tvorba-uzivatelskych-maker/ - Clojure 16: Složitější uživatelská makra
http://www.root.cz/clanky/programovaci-jazyk-clojure-16-slozitejsi-uzivatelska-makra/ - Clojure 17: Využití standardních maker v praxi
http://www.root.cz/clanky/programovaci-jazyk-clojure-17-vyuziti-standardnich-maker-v-praxi/ - Clojure 18: Základní techniky optimalizace aplikací
http://www.root.cz/clanky/programovaci-jazyk-clojure-18-zakladni-techniky-optimalizace-aplikaci/ - Clojure 19: Vývojová prostředí pro Clojure
http://www.root.cz/clanky/programovaci-jazyk-clojure-19-vyvojova-prostredi-pro-clojure/ - 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/ - Clojure 21: ClojureScript aneb překlad Clojure do JS
http://www.root.cz/clanky/programovaci-jazyk-clojure-21-clojurescript-aneb-preklad-clojure-do-javascriptu/ - Leiningen: nástroj pro správu projektů napsaných v Clojure
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure/ - 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/ - 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/ - 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/ - 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/ - 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/ - Programovací jazyk Clojure a databáze (1.část)
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-databaze-1-cast/ - Pluginy pro Leiningen
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-pluginy-pro-leiningen/ - 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/ - 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/ - 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/ - 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/ - 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/ - Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure
http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure/ - 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/ - 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/ - Programovací jazyk Clojure a práce s Gitem
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem/ - Programovací jazyk Clojure a práce s Gitem (2)
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem-2/ - Programovací jazyk Clojure – triky při práci s řetězci
http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-retezci/ - Programovací jazyk Clojure – triky při práci s kolekcemi
http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-kolekcemi/ - Programovací jazyk Clojure – práce s mapami a množinami
http://www.root.cz/clanky/programovaci-jazyk-clojure-prace-s-mapami-a-mnozinami/ - Programovací jazyk Clojure – základy zpracování XML
http://www.root.cz/clanky/programovaci-jazyk-clojure-zaklady-zpracovani-xml/ - Programovací jazyk Clojure – testování s využitím knihovny Expectations
http://www.root.cz/clanky/programovaci-jazyk-clojure-testovani-s-vyuzitim-knihovny-expectations/ - 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/ - Enlive – výkonný šablonovací systém pro jazyk Clojure
http://www.root.cz/clanky/enlive-vykonny-sablonovaci-system-pro-jazyk-clojure/ - 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/ - Novinky v Clojure verze 1.8.0
http://www.root.cz/clanky/novinky-v-clojure-verze-1–8–0/ - 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/ - 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/ - 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/
14. Odkazy na Internetu
- Communicating sequential processes
https://en.wikipedia.org/wiki/Communicating_sequential_processes - Clojure core.async
http://www.infoq.com/presentations/clojure-core-async - core.async API Reference
https://clojure.github.io/core.async/ - Clojure core.async Channels
http://clojure.com/blog/2013/06/28/clojure-core-async-channels.html - core.async examples
https://github.com/clojure/core.async/blob/master/examples/walkthrough.clj - Timothy Baldridge – Core.Async
https://www.youtube.com/watch?v=enwIIGzhahw - Designing Front End Applications with core.async
http://go.cognitect.com/core_async_webinar_recording - Mastering Concurrent Processes with core.async
http://www.braveclojure.com/core-async/ - LispCast: Clojure core.async
https://www.youtube.com/watch?v=msv8Fvtd6YQ - Julian Gamble – Applying the paradigms of core.async in ClojureScript
https://www.youtube.com/watch?v=JUrOebC5HmA - Zip archiv s Clojure 1.8.0
http://repo1.maven.org/maven2/org/clojure/clojure/1.8.0/clojure-1.8.0.zip - Clojure 1.8 is now available
http://clojure.org/news/2016/01/19/clojure18 - Changes to Clojure in Version 1.8
https://github.com/clojure/clojure/blob/master/changes.md - Socket Server REPL
http://dev.clojure.org/display/design/Socket+Server+REPL - CLJ-1671: Clojure socket server
http://dev.clojure.org/jira/browse/CLJ-1671 - CLJ-1449: Add clojure.string functions for portability to ClojureScript
http://dev.clojure.org/jira/browse/CLJ-1449 - Launching a Socket Server
http://clojure.org/reference/repl_and_main#_launching_a_socket_server - API for clojure.string
http://clojure.github.io/clojure/branch-master/clojure.string-api.html - Clojars:
https://clojars.org/ - Seznam knihoven na Clojars:
https://clojars.org/projects - Clojure Cookbook: Templating HTML with Enlive
https://github.com/clojure-cookbook/clojure-cookbook/blob/master/07_webapps/7–11_enlive.asciidoc - An Introduction to Enlive
https://github.com/swannodette/enlive-tutorial/ - Enlive na GitHubu
https://github.com/cgrand/enlive - Expectations: příklady atd.
http://jayfields.com/expectations/ - Expectations na GitHubu
https://github.com/jaycfields/expectations - Lein-expectations na GitHubu
https://github.com/gar3thjon3s/lein-expectations - Testing Clojure With Expectations
https://semaphoreci.com/blog/2014/09/23/testing-clojure-with-expectations.html - Clojure testing TDD/BDD libraries: clojure.test vs Midje vs Expectations vs Speclj
https://www.reddit.com/r/Clojure/comments/1viilt/clojure_testing_tddbdd_libraries_clojuretest_vs/ - Testing: One assertion per test
http://blog.jayfields.com/2007/06/testing-one-assertion-per-test.html - Rewriting Your Test Suite in Clojure in 24 hours
http://blog.circleci.com/rewriting-your-test-suite-in-clojure-in-24-hours/ - Clojure doc: zipper
http://clojuredocs.org/clojure.zip/zipper - Clojure doc: parse
http://clojuredocs.org/clojure.xml/parse - Clojure doc: xml-zip
http://clojuredocs.org/clojure.zip/xml-zip - Clojure doc: xml-seq
http://clojuredocs.org/clojure.core/xml-seq - Parsing XML in Clojure
https://github.com/clojuredocs/guides - Clojure Zipper Over Nested Vector
https://vitalyper.wordpress.com/2010/11/23/clojure-zipper-over-nested-vector/ - Understanding Clojure's PersistentVector implementation
http://blog.higher-order.net/2009/02/01/understanding-clojures-persistentvector-implementation - Understanding Clojure's PersistentHashMap (deftwice…)
http://blog.higher-order.net/2009/09/08/understanding-clojures-persistenthashmap-deftwice.html - Assoc and Clojure's PersistentHashMap: part ii
http://blog.higher-order.net/2010/08/16/assoc-and-clojures-persistenthashmap-part-ii.html - Ideal Hashtrees (paper)
http://lampwww.epfl.ch/papers/idealhashtrees.pdf - Clojure home page
http://clojure.org/ - Clojure (downloads)
http://clojure.org/downloads - Clojure Sequences
http://clojure.org/sequences - Clojure Data Structures
http://clojure.org/data_structures - The Structure and Interpretation of Computer Programs: 2.2.1 Representing Sequences
http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-15.html#%_sec2.2.1 - The Structure and Interpretation of Computer Programs: 3.3.1 Mutable List Structure
http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-22.html#%_sec3.3.1 - Clojure – Functional Programming for the JVM
http://java.ociweb.com/mark/clojure/article.html - Clojure quick reference
http://faustus.webatu.com/clj-quick-ref.html - 4Clojure
http://www.4clojure.com/ - ClojureDoc (rozcestník s dokumentací jazyka Clojure)
http://clojuredocs.org/ - Clojure (na Wikipedia EN)
http://en.wikipedia.org/wiki/Clojure - Clojure (na Wikipedia CS)
http://cs.wikipedia.org/wiki/Clojure - SICP (The Structure and Interpretation of Computer Programs)
http://mitpress.mit.edu/sicp/ - Pure function
http://en.wikipedia.org/wiki/Pure_function - Funkcionální programování
http://cs.wikipedia.org/wiki/Funkcionální_programování - Čistě funkcionální (datové struktury, jazyky, programování)
http://cs.wikipedia.org/wiki/Čistě_funkcionální - Clojure Macro Tutorial (Part I, Getting the Compiler to Write Your Code For You)
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-i-getting.html - Clojure Macro Tutorial (Part II: The Compiler Strikes Back)
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-ii-compiler.html - Clojure Macro Tutorial (Part III: Syntax Quote)
http://www.learningclojure.com/2010/09/clojure-macro-tutorial-part-ii-syntax.html - Tech behind Tech: Clojure Macros Simplified
http://techbehindtech.com/2010/09/28/clojure-macros-simplified/ - Fatvat – Exploring functional programming: Clojure Macros
http://www.fatvat.co.uk/2009/02/clojure-macros.html - Eulerovo číslo
http://cs.wikipedia.org/wiki/Eulerovo_číslo - List comprehension
http://en.wikipedia.org/wiki/List_comprehension - List Comprehensions in Clojure
http://asymmetrical-view.com/2008/11/18/list-comprehensions-in-clojure.html - Clojure Programming Concepts: List Comprehension
http://en.wikibooks.org/wiki/Clojure_Programming/Concepts#List_Comprehension - Clojure core API: for macro
http://clojure.github.com/clojure/clojure.core-api.html#clojure.core/for - cirrus machina – The Clojure for macro
http://www.cirrusmachina.com/blog/comment/the-clojure-for-macro/ - Riastradh's Lisp Style Rules
http://mumble.net/~campbell/scheme/style.txt - Dynamic Languages Strike Back
http://steve-yegge.blogspot.cz/2008/05/dynamic-languages-strike-back.html - Scripting: Higher Level Programming for the 21st Century
http://www.tcl.tk/doc/scripting.html - Java Virtual Machine Support for Non-Java Languages
http://docs.oracle.com/javase/7/docs/technotes/guides/vm/multiple-language-support.html - Třída java.lang.String
http://docs.oracle.com/javase/7/docs/api/java/lang/String.html - Třída java.lang.StringBuffer
http://docs.oracle.com/javase/7/docs/api/java/lang/StringBuffer.html - Třída java.lang.StringBuilder
http://docs.oracle.com/javase/7/docs/api/java/lang/StringBuilder.html - StringBuffer versus String
http://www.javaworld.com/article/2076072/build-ci-sdlc/stringbuffer-versus-string.html - Threading macro (dokumentace k jazyku Clojure)
https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/-> - Understanding the Clojure → macro
http://blog.fogus.me/2009/09/04/understanding-the-clojure-macro/ - clojure.inspector
http://clojure.github.io/clojure/clojure.inspector-api.html - The Clojure Toolbox
http://www.clojure-toolbox.com/ - Unit Testing in Clojure
http://nakkaya.com/2009/11/18/unit-testing-in-clojure/ - Testing in Clojure (Part-1: Unit testing)
http://blog.knoldus.com/2014/03/22/testing-in-clojure-part-1-unit-testing/ - API for clojure.test – Clojure v1.6 (stable)
https://clojure.github.io/clojure/clojure.test-api.html - Leiningen: úvodní stránka
http://leiningen.org/ - Leiningen: Git repository
https://github.com/technomancy/leiningen - leiningen-win-installer
http://leiningen-win-installer.djpowell.net/ - Clojure.org: Vars and the Global Environment
http://clojure.org/Vars - Clojure.org: Refs and Transactions
http://clojure.org/Refs - Clojure.org: Atoms
http://clojure.org/Atoms - Clojure.org: Agents as Asynchronous Actions
http://clojure.org/agents - Transient Data Structureshttp://clojure.org/transients