Vytváříme IRC bota v programovacím jazyce Clojure

Pavel Tišnovský 23. 2. 2016

Dnešní část seriálu o jazyce Clojure bude zaměřena praktičtěji než předchozí tři části, protože si ukážeme, jakým způsobem je možné vytvořit jednoduchého IRC bota, a to s využitím knihovny nazvané irclj.

Obsah

1. Vytváříme IRC bota v programovacím jazyce Clojure

2. Knihovna irclj

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

14. Odkazy na Internetu

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).

widgety

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:

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. 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/
  35. Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure
    http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure/
  36. 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/
  37. 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/
  38. Programovací jazyk Clojure a práce s Gitem
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem/
  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/

14. Odkazy na Internetu

  1. Communicating sequential processes
    https://en.wikipedia.org/wi­ki/Communicating_sequenti­al_processes
  2. Clojure core.async
    http://www.infoq.com/presen­tations/clojure-core-async
  3. core.async API Reference
    https://clojure.github.io/core.async/
  4. Clojure core.async Channels
    http://clojure.com/blog/2013/06/28/clo­jure-core-async-channels.html
  5. core.async examples
    https://github.com/clojure/co­re.async/blob/master/exam­ples/walkthrough.clj
  6. Timothy Baldridge – Core.Async
    https://www.youtube.com/wat­ch?v=enwIIGzhahw
  7. Designing Front End Applications with core.async
    http://go.cognitect.com/co­re_async_webinar_recording
  8. Mastering Concurrent Processes with core.async
    http://www.braveclojure.com/core-async/
  9. LispCast: Clojure core.async
    https://www.youtube.com/wat­ch?v=msv8Fvtd6YQ
  10. Julian Gamble – Applying the paradigms of core.async in ClojureScript
    https://www.youtube.com/wat­ch?v=JUrOebC5HmA
  11. Zip archiv s Clojure 1.8.0
    http://repo1.maven.org/ma­ven2/org/clojure/clojure/1­.8.0/clojure-1.8.0.zip
  12. Clojure 1.8 is now available
    http://clojure.org/news/2016/01/19/clo­jure18
  13. Changes to Clojure in Version 1.8
    https://github.com/clojure/clo­jure/blob/master/changes.md
  14. Socket Server REPL
    http://dev.clojure.org/dis­play/design/Socket+Server+REPL
  15. CLJ-1671: Clojure socket server
    http://dev.clojure.org/jira/browse/CLJ-1671
  16. CLJ-1449: Add clojure.string functions for portability to ClojureScript
    http://dev.clojure.org/jira/browse/CLJ-1449
  17. Launching a Socket Server
    http://clojure.org/referen­ce/repl_and_main#_launchin­g_a_socket_server
  18. API for clojure.string
    http://clojure.github.io/clo­jure/branch-master/clojure.string-api.html
  19. Clojars:
    https://clojars.org/
  20. Seznam knihoven na Clojars:
    https://clojars.org/projects
  21. Clojure Cookbook: Templating HTML with Enlive
    https://github.com/clojure-cookbook/clojure-cookbook/blob/master/07_webapps/7–11_enlive.asciidoc
  22. An Introduction to Enlive
    https://github.com/swannodette/enlive-tutorial/
  23. Enlive na GitHubu
    https://github.com/cgrand/enlive
  24. Expectations: příklady atd.
    http://jayfields.com/expectations/
  25. Expectations na GitHubu
    https://github.com/jaycfi­elds/expectations
  26. Lein-expectations na GitHubu
    https://github.com/gar3thjon3s/lein-expectations
  27. Testing Clojure With Expectations
    https://semaphoreci.com/blog/2014/09/23/tes­ting-clojure-with-expectations.html
  28. Clojure testing TDD/BDD libraries: clojure.test vs Midje vs Expectations vs Speclj
    https://www.reddit.com/r/Clo­jure/comments/1viilt/cloju­re_testing_tddbdd_librari­es_clojuretest_vs/
  29. Testing: One assertion per test
    http://blog.jayfields.com/2007/06/tes­ting-one-assertion-per-test.html
  30. Rewriting Your Test Suite in Clojure in 24 hours
    http://blog.circleci.com/rewriting-your-test-suite-in-clojure-in-24-hours/
  31. Clojure doc: zipper
    http://clojuredocs.org/clo­jure.zip/zipper
  32. Clojure doc: parse
    http://clojuredocs.org/clo­jure.xml/parse
  33. Clojure doc: xml-zip
    http://clojuredocs.org/clojure.zip/xml-zip
  34. Clojure doc: xml-seq
    http://clojuredocs.org/clo­jure.core/xml-seq
  35. Parsing XML in Clojure
    https://github.com/clojuredocs/guides
  36. Clojure Zipper Over Nested Vector
    https://vitalyper.wordpres­s.com/2010/11/23/clojure-zipper-over-nested-vector/
  37. Understanding Clojure's PersistentVector implementation
    http://blog.higher-order.net/2009/02/01/understanding-clojures-persistentvector-implementation
  38. Understanding Clojure's PersistentHashMap (deftwice…)
    http://blog.higher-order.net/2009/09/08/understanding-clojures-persistenthashmap-deftwice.html
  39. Assoc and Clojure's PersistentHashMap: part ii
    http://blog.higher-order.net/2010/08/16/assoc-and-clojures-persistenthashmap-part-ii.html
  40. Ideal Hashtrees (paper)
    http://lampwww.epfl.ch/pa­pers/idealhashtrees.pdf
  41. Clojure home page
    http://clojure.org/
  42. Clojure (downloads)
    http://clojure.org/downloads
  43. Clojure Sequences
    http://clojure.org/sequences
  44. Clojure Data Structures
    http://clojure.org/data_structures
  45. 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
  46. 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
  47. Clojure – Functional Programming for the JVM
    http://java.ociweb.com/mar­k/clojure/article.html
  48. Clojure quick reference
    http://faustus.webatu.com/clj-quick-ref.html
  49. 4Clojure
    http://www.4clojure.com/
  50. ClojureDoc (rozcestník s dokumentací jazyka Clojure)
    http://clojuredocs.org/
  51. Clojure (na Wikipedia EN)
    http://en.wikipedia.org/wiki/Clojure
  52. Clojure (na Wikipedia CS)
    http://cs.wikipedia.org/wiki/Clojure
  53. SICP (The Structure and Interpretation of Computer Programs)
    http://mitpress.mit.edu/sicp/
  54. Pure function
    http://en.wikipedia.org/wi­ki/Pure_function
  55. Funkcionální programování
    http://cs.wikipedia.org/wi­ki/Funkcionální_programová­ní
  56. Čistě funkcionální (datové struktury, jazyky, programování)
    http://cs.wikipedia.org/wi­ki/Čistě_funkcionální
  57. 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
  58. Clojure Macro Tutorial (Part II: The Compiler Strikes Back)
    http://www.learningclojure­.com/2010/09/clojure-macro-tutorial-part-ii-compiler.html
  59. Clojure Macro Tutorial (Part III: Syntax Quote)
    http://www.learningclojure­.com/2010/09/clojure-macro-tutorial-part-ii-syntax.html
  60. Tech behind Tech: Clojure Macros Simplified
    http://techbehindtech.com/2010/09/28/clo­jure-macros-simplified/
  61. Fatvat – Exploring functional programming: Clojure Macros
    http://www.fatvat.co.uk/2009/02/clo­jure-macros.html
  62. Eulerovo číslo
    http://cs.wikipedia.org/wi­ki/Eulerovo_číslo
  63. List comprehension
    http://en.wikipedia.org/wi­ki/List_comprehension
  64. List Comprehensions in Clojure
    http://asymmetrical-view.com/2008/11/18/list-comprehensions-in-clojure.html
  65. Clojure Programming Concepts: List Comprehension
    http://en.wikibooks.org/wi­ki/Clojure_Programming/Con­cepts#List_Comprehension
  66. Clojure core API: for macro
    http://clojure.github.com/clo­jure/clojure.core-api.html#clojure.core/for
  67. cirrus machina – The Clojure for macro
    http://www.cirrusmachina.com/blog/com­ment/the-clojure-for-macro/
  68. Riastradh's Lisp Style Rules
    http://mumble.net/~campbe­ll/scheme/style.txt
  69. Dynamic Languages Strike Back
    http://steve-yegge.blogspot.cz/2008/05/dynamic-languages-strike-back.html
  70. Scripting: Higher Level Programming for the 21st Century
    http://www.tcl.tk/doc/scripting.html
  71. Java Virtual Machine Support for Non-Java Languages
    http://docs.oracle.com/ja­vase/7/docs/technotes/gui­des/vm/multiple-language-support.html
  72. Třída java.lang.String
    http://docs.oracle.com/ja­vase/7/docs/api/java/lang/Strin­g.html
  73. Třída java.lang.StringBuffer
    http://docs.oracle.com/ja­vase/7/docs/api/java/lang/Strin­gBuffer.html
  74. Třída java.lang.StringBuilder
    http://docs.oracle.com/ja­vase/7/docs/api/java/lang/Strin­gBuilder.html
  75. StringBuffer versus String
    http://www.javaworld.com/ar­ticle/2076072/build-ci-sdlc/stringbuffer-versus-string.html
  76. Threading macro (dokumentace k jazyku Clojure)
    https://clojure.github.io/clo­jure/clojure.core-api.html#clojure.core/->
  77. Understanding the Clojure → macro
    http://blog.fogus.me/2009/09/04/un­derstanding-the-clojure-macro/
  78. clojure.inspector
    http://clojure.github.io/clo­jure/clojure.inspector-api.html
  79. The Clojure Toolbox
    http://www.clojure-toolbox.com/
  80. Unit Testing in Clojure
    http://nakkaya.com/2009/11/18/unit-testing-in-clojure/
  81. Testing in Clojure (Part-1: Unit testing)
    http://blog.knoldus.com/2014/03/22/tes­ting-in-clojure-part-1-unit-testing/
  82. API for clojure.test – Clojure v1.6 (stable)
    https://clojure.github.io/clo­jure/clojure.test-api.html
  83. Leiningen: úvodní stránka
    http://leiningen.org/
  84. Leiningen: Git repository
    https://github.com/techno­mancy/leiningen
  85. leiningen-win-installer
    http://leiningen-win-installer.djpowell.net/
  86. Clojure.org: Vars and the Global Environment
    http://clojure.org/Vars
  87. Clojure.org: Refs and Transactions
    http://clojure.org/Refs
  88. Clojure.org: Atoms
    http://clojure.org/Atoms
  89. Clojure.org: Agents as Asynchronous Actions
    http://clojure.org/agents
  90. Transient Data Structureshttp://clojure.or­g/transients
Našli jste v článku chybu?
Podnikatel.cz: Nemá dluhy? Zjistíte to na poště

Nemá dluhy? Zjistíte to na poště

DigiZone.cz: Ve sběru baterií jsme pilní

Ve sběru baterií jsme pilní

Lupa.cz: Blíží se konec Wi-Fi sítí bez hesla?

Blíží se konec Wi-Fi sítí bez hesla?

Podnikatel.cz: Byla finanční manažerka, teď cvičí jógu

Byla finanční manažerka, teď cvičí jógu

Podnikatel.cz: Letáky? Lidi zuří, ale ony stále fungují

Letáky? Lidi zuří, ale ony stále fungují

Vitalia.cz: 5 chyb, které děláme při skladování potravin

5 chyb, které děláme při skladování potravin

Vitalia.cz: Pryč se zastaralým stravováním ve školách

Pryč se zastaralým stravováním ve školách

Měšec.cz: TEST: Vyzkoušeli jsme pražské taxikáře

TEST: Vyzkoušeli jsme pražské taxikáře

Podnikatel.cz: ČSSZ posílá přehled o důchodovém kontě

ČSSZ posílá přehled o důchodovém kontě

Vitalia.cz: Tradiční čínská medicína a rakovina

Tradiční čínská medicína a rakovina

Lupa.cz: Adblock Plus začal prodávat reklamy

Adblock Plus začal prodávat reklamy

Vitalia.cz: Voda z Vltavy před a po úpravě na pitnou

Voda z Vltavy před a po úpravě na pitnou

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

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

Podnikatel.cz: Tyto pojmy k #EET byste měli znát

Tyto pojmy k #EET byste měli znát

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

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

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

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

DigiZone.cz: Technisat připravuje trojici DAB

Technisat připravuje trojici DAB

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

Nova opět stahuje „milionáře“

DigiZone.cz: Ginx TV: pořad o počítačových hráčích

Ginx TV: pořad o počítačových hráčích

120na80.cz: Co je padesátkrát sladší než cukr?

Co je padesátkrát sladší než cukr?