Obsah
1. Programovací jazyk Clojure – triky při práci s řetězci
2. Funkce pro práci s řetězci ve jmenném prostoru clojure.string
3. Metody třídy java.lang.String
4. Konstrukce řetězce z několika částí
5. Test, zda je řetězec prázdný
6. Převody mezi verzálkami a minuskami
7. Funkce clojure.string/escape
8. Funkce clojure.string/replace
9. Funkce pro spojování a rozdělování řetězců
10. Řetězce chápané jako sekvence znaků
11. Odkazy na předchozí části tohoto seriálu
1. Programovací jazyk Clojure – triky při práci s řetězci
Již v perexu tohoto článku bylo napsáno, že se v dnešní části seriálu o programovacím jazyce Clojure i o knihovnách, které pro tento jazyk existují, budeme zabývat popisem některých tipů a triků využitelných při práci s řetězci. Je tomu tak ze dvou důvodů – řetězce a jejich zpracování jsou nedílnou součástí většiny nenumerických algoritmů a navíc jsem z dotazů zjistil, že i ti vývojáři, kteří v jazyku Clojure začali programovat, mnohdy nevyužívají všech možností nabízených zejména funkcemi ve jmenném prostoru clojure.string a zbytečně pak musí volat metody třídy java.lang.String (tedy využívat tzv. java interop), což ovšem není totéž (už jen z toho důvodu, že většina funkcí pro práci s řetězci bez problému „přežije“ předání hodnoty nil, zatímco v případě javovských metod tomu tak pochopitelně není).
Důležité je si ihned na začátku práce s řetězci uvědomit, že řetězce jsou – ostatně podobně jako je tomu i v Javě – konstantní a tudíž i neměnné (immutable), což sice v některých případech může vést k tvorbě neefektivních operací, kterým se však lze v Clojure většinou zcela vyhnout. To, že jsou řetězce neměnné, však zjednodušuje tvorbu bezpečných vícevláknových aplikací, řetězce lze efektivně využívat jako klíče do asociativních polí atd. atd., takže přednosti většinou převažují nad zápory. Pokud by tomu tak nebylo a některý algoritmus by skutečně vyžadoval modifikovatelné řetězce, je možné využít možností knihoven java.lang.StringBuffer a java.lang.StringBuilder [1] [2].
2. Funkce pro práci s řetězci ve jmenném prostoru clojure.string
Většina užitečných funkcí pro práci s řetězci se nachází ve jmenném prostoru clojure.string. Jedná se o následující funkce:
# | Funkce | Kapitola |
---|---|---|
1 | blank? | 5 |
2 | capitalize | 6 |
3 | escape | 7 |
4 | join | 9 |
5 | lower-case | 6 |
6 | re-quote-replacement | |
7 | replace | 8 |
8 | replace-first | 8 |
9 | reverse | |
10 | split | 9 |
11 | split-lines | 9 |
12 | trim | |
13 | trim-newline | |
14 | triml | |
15 | trimr | |
16 | upper-case | 6 |
V následujících kapitolách budu předpokládat, že prvním příkazem skriptu bude načtení jmenného prostoru clojure.string s aliasem str:
user=> (require '[clojure.string :as str]) nil
Poznámka: nepoužívejte příkaz use namísto require, protože by došlo ke smíchání symbolů z načítaného modulu se symboly vaší aplikace, což znesnadňuje ladění (aneb „use considered harmful“.
3. Metody třídy java.lang.String
Kromě výše zmíněných funkcí dostupných ze jmenného prostoru clojure.string lze v programovacím jazyce Clojure díky existenci tzv. Java interop volat i metody třídy java.lang.String. Platí přitom, že řetězec vytvořený v Clojure či řetězcový literál je současně i javovským řetězcem se všemi z toho vyplývajícími vlastnostmi a omezeními (kódování UTF-16 atd.):
user=> (type "Hello World") java.lang.String
Připomeňme si, že volání metod nějakého javovského objektu vypadá následovně:
(.názevMetody objekt parametry)
Takže lze například použít formu:
user=> (.length "Hello world!") 12 user=> (.getClass "Hello world!") java.lang.String user=> (.substring "Hello world!" 5 8) " wo" user=> (.hashCode "Hello world!") -52966915 user=> (.charAt "Hello world!" 11) \!
Popis všech metod třídy java.lang.String lze nalézt na adrese http://docs.oracle.com/javase/7/docs/api/java/lang/String.html.
4. Konstrukce řetězce z několika částí
Poměrně často se setkáme s nutností zkonstruovat řetězec z několika částí popř. převést jiný objekt (přesněji řečeno jinou hodnotu) na řetězec. Zde je řešení snadné, protože je možné použít víceúčelovou funkci nazvanou str. Tato funkce akceptuje libovolný počet parametrů libovolných typů, dokonce se ani nemusíte bát ji zavolat s hodnotou nil (ostatně předávání této hodnoty do mnoha funkcí bez kontroly je pro jazyk Clojure idiomatické, na rozdíl od Javy):
user=> (str nil) "" user=> (str 1 2 3) "123" user=> (str [1 2 3]) "[1 2 3]" user=> (str "first" "second" "third") "firstsecondthird" user=> (str "first" "second") "firstsecond"
Zastavme se u chvíli u posledního příkladu. V mnoha aplikacích potřebujeme vypsat na standardní výstup nějaké hodnoty, což (samozřejmě) zajistí funkce println:
user=> (println "first" "second") first second nil
Pokud z nějakého důvodu nevyhovuje oddělení hodnot mezerou, pomůže právě funkce str:
user=> (println (str "first" "second")) firstsecond nil
5. Test, zda je řetězec prázdný
Pro test, zda je řetězec prázdný, je možné použít buď metodu .isEmpty třídy java.lang.String, funkci empty? nebo funkci clojure.string/blank? (povšimněte si otazníku na konci názvu této funkce). Čím se tyto možnosti od sebe odlišují? Hlavním rozdílem je, že clojure.string/blank? bez problémů akceptuje hodnotu nil (považovanou za prázdný řetězec), což je v mnoha aplikacích preferováno. Další rozdíl spočívá v tom, že clojure.string/blank? vrací pravdivostní hodnotu true i pro řetězce obsahující pouze bílé znaky, na rozdíl od empty?:
user=> (empty? nil) true user=> (empty? "") true user=> (empty? " ") false user=> (empty? "Hello world!") false
user=> (str/blank? nil) true user=> (str/blank? "") true user=> (str/blank? " ") true user=> (str/blank? "Hello world!") false
user=> (.isEmpty nil) NullPointerException clojure.lang.Reflector.invokeNoArgInstanceMember (Reflector.java:301) user=> (.isEmpty "") true user=> (.isEmpty " ") false user=> (.isEmpty "Hello world!") false
6. Převody mezi verzálkami a minuskami
V některých aplikacích je nutné převádět minusky na verzálky a naopak. Opět je možné si vybrat mezi metodami třídy java.lang.String a funkcemi jmenného prostoru clojure.string. Podívejme se ihned na příklady, z nichž je použití těchto funkcí pravděpodobně zřejmé:
user=> (.toLowerCase "Hello world!") "hello world!" user=> (str/upper-case "Hello world!") "HELLO WORLD!" user=> (str/lower-case "Hello world!") "hello world!" user=> (str/capitalize "Hello world!") "Hello world!" user=> (str/capitalize "HeLLo wOrld!") "Hello world!
A jak jsme na tom s nabodeníčky? Docela dobře, však jsme v 21. století:
user=> (str/upper-case "Příliš žluťoučký kůň úpěl ďábelské ódy") "PŘÍLIŠ ŽLUŤOUČKÝ KŮŇ ÚPĚL ĎÁBELSKÉ ÓDY" user=> (str/lower-case "Příliš žluťoučký kůň úpěl ďábelské ódy") "příliš žluťoučký kůň úpěl ďábelské ódy" user=> (str/capitalize "Příliš žluťoučký kůň úpěl ďábelské ódy") "Příliš žluťoučký kůň úpěl ďábelské ódy"
7. Funkce clojure.string/escape
Konečně se dostáváme k zajímavějším funkcím. Poměrně užitečnou funkcí je funkce nazvaná clojure.string/escape, které lze předat převodní tabulku (znak na znak, znak na řetězec atd.); jedná se vlastně o určitou obdobu funkce tr. Zmíněná převodní tabulka má podobu mapy:
user=> (str/escape "Hello world!" {\l "*"}) "He**o wor*d!" user=> (str/escape "Hello world!" {\l "*" \o \-}) "He**- w-r*d!"
Podívejme se na použití této funkce ve skutečné aplikaci, konkrétně na místě, kde je zapotřebí nahrazovat trojici znaků, které mají speciální význam v XML či HTML:
(defn escape-xml-chars [string] (clojure.string/escape string {\< "<", \> ">", \& "&"})) (escape-xml-chars "Hello <world>") "Hello <world>
8. Funkce clojure.string/replace
Jedna z nejužitečnějších funkcí při práci s řetězci je funkce clojure.string/replace. V nejjednodušší podobě se této funkci předává řetězec, nahrazovaná sekvence znaků a řetězec, který se použije namísto nahrazované sekvence znaků:
user=> (str/replace "Hello world!" " " "_") "Hello_world!" user=> (str/replace "Hello world!" "o" "0") "Hell0 w0rld!" user=> (str/replace "Hello world!" " " "___") "Hello___world!" user=> (str/replace "Hello world!" "world" "Clojure") "Hello Clojure!"
V alternativní podobě je možné namísto nahrazovaného řetězce použít regulární výraz. Připomeňme si, že pro regulární výrazy v Clojure existuje speciální zápis se znakem #. Díky tomu není nutné mnoho znaků ve výrazu quotovat tak, jako je tomu v mnoha dalších programovacích jazycích:
user=> (str/replace "Hello world!" #"[a-z]" ".") "H.... .....!" user=> (str/replace "Hello world!" #"[A-Za-z]+" "[word]") "[word] [word]!" user=> (str/replace "Hello world!" #"\w+" "[word]") "[word] [word]!" user=> (str/replace "Hello world!" #"\s" "_") "Hello_world!"
Nejzajímavější je ovšem třetí podoba, kde se namísto nahrazovacího řetězce použije funkce, typicky funkce anonymní. V této funkci je možné použít speciální jména parametrů %1 atd., do nichž se uloží hodnoty zachycené do příslušného registru. V následujícím příkladu se *slovo* zvýrazní jako slovo, podobně jako je tomu v AsciiDocu (povšimněte si použití zpětného lomítka před znakem hvězdičky):
user=> (str/replace "Hello world!" #"\*(\w+)\*" #(str "<strong>" (second %1) "</strong>")) "Hello world!" user=> (str/replace "Hello *world*!" #"\*(\w+)\*" #(str "<strong>" (second %1) "</strong>")) "Hello <strong>world</strong>!" user=> (str/replace "*Hello* *world*!" #"\*(\w+)\*" #(str "<strong>" (second %1) "</strong>")) "<strong>Hello</strong> <strong>world</strong>!"
Kdo se ještě neztratil, může si vyzkoušet následující regulární výraz, který bude posléze využitý:
(def http-regexp #"https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)") user=> (re-find http-regexp "xyzzy") nil user=> (re-find http-regexp "Podivejte se na http://www.root.cz!") ["http://www.root.cz" "www." ""] user=> (re-find http-regexp "Na http://www.google.com jsem to nenasel.") ["http://www.google.com" "www." ""]
Nyní regulární výraz navázaný na symbol http-regexp využijeme ve funkci, která v řetězci najde URL a nahradí je příslušnou HTML značkou:
(defn create-links [string] (let [href (re-find http-regexp string)] (if href (str/replace string http-regexp (str "<a href='" (first href) "'>" (first href) "</a>")) string))) user=> (create-links "Podivejte se na http://www.root.cz!") "Podivejte se na <a href='http://www.root.cz'>http://www.root.cz</a>!" user=> (create-links "Na http://www.google.com jsem to nenasel.") "Na <a href='http://www.google.com'>http://www.google.com</a> jsem to nenasel."
9. Funkce pro spojování a rozdělování řetězců
Pro spojení řetězců se používá funkce clojure.string/join, které je možné v prvním parametru předat znak (či řetězec), který se vloží mezi spojované části. Použití této funkce je snadné:
user=> (str/join (range 1 10)) "123456789" user=> (str/join "," (range 1 10)) "1,2,3,4,5,6,7,8,9"
Pro rozdělení řetězce na části lze použít funkci clojure.string/split. V mnoha případech je však snadnější použít clojure.string/split-lines, která řetězec rozdělí na znacích pro nový řádek. To je velmi výhodné, například při načítání konfiguračních či databázových souborů:
user=> (str/split-lines "Hello\nworld") ["Hello" "world"]
Podívejme se na praktický příklad:
(slurp "/etc/passwd") (str/split-lines (slurp "/etc/passwd")) (for [line (str/split-lines (slurp "/etc/passwd"))] (first (str/split line #":")))
Výsledek (jména uživatelů) lze setřídit:
(sort (for [line (str/split-lines (slurp "/etc/passwd"))] (first (str/split line #":"))))
10. Řetězce chápané jako sekvence znaků
Řetězce lze funkcí seq rozdělit na sekvenci znaků a použít tak všechny funkce, které Clojure pro práci se sekvencemi nabízí (třeba i funkci frequencies):
user=> (seq "hello world!") (\h \e \l \l \o \space \w \o \r \l \d \!) user=> (-> (concat (range 1 12) (range 3 10) [6]) frequencies) {7 2, 1 1, 4 2, 6 3, 3 2, 2 1, 11 1, 9 2, 5 2, 10 1, 8 2} user=> (frequencies (seq "hello world!")) {\space 1, \! 1, \d 1, \e 1, \h 1, \l 3, \o 2, \r 1, \w 1}
11. Odkazy na předchozí části tohoto seriálu
Stalo se již zvykem uvést odkazy na všechny předchozí části tohoto seriálu. Tento zvyk samozřejmě dodržíme i dnes:
- 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/
12. Odkazy na Internetu
- Clojure home page
http://clojure.org/downloads - 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
http://clojuredocs.org/ - Clojure (Wikipedia EN)
http://en.wikipedia.org/wiki/Clojure - Clojure (Wikipedia CS)
http://cs.wikipedia.org/wiki/Clojure - 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 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/