Programovací jazyk Clojure 14: základy práce se systémem maker

Pavel Tišnovský 11. 9. 2012

V dnešní části seriálu o Javě, JVM i navazujících technologiích si popíšeme další důležitou vlastnost programovacího jazyka Clojure. Tento jazyk je totiž, podobně jako další jazyky inspirované LISPem, vybaven mocným systémem maker. V samotném Clojure jsou již některá makra pevně zabudována a další lze relativně jednoduše doplnit.

Obsah

1. Programovací jazyk Clojure 14: základy práce se systémem maker

2. Smyčka REPL (Read-Eval-Print-Loop) a systém maker v tradičních LISPech

3. První písmeno ve zkratce REPL: objekt Reader a jeho makra

4. Makra „comment“, „character“ a „metadata“

5. Makro „deref“

6. Makra „quote“ a „syntax-quote“

7. Makra „unquote“ a „unquote-splicing“

8. Makro „dispatch“

9. Odkazy na Internetu

1. Programovací jazyk Clojure 14: základy práce se systémem maker

V dnešní části seriálu o programovacím jazyku Java i o vlastnostech virtuálního stroje Javy se opět budeme zabývat popisem zajímavých vlastností programovacího jazyka Clojure, jehož původní implementace je postavená právě nad virtuálním strojem Javy (JVM). Dnes si řekneme základní informace o tom, jak lze v Clojure používat makra, která mohou velmi významným způsobem ovlivnit způsob tvorby programů, protože právě díky použití maker lze v Clojure vytvářet takzvané doménově orientované jazyky – například se může jedna o obdobu jazyka SQL atd. Již při popisu základních principů, na nichž je postaven programovací jazyk Clojure jsme si řekli, že je tento jazyk vybaven interpretrem kombinovaným s „utajeným“ překladačem zapisovaných funkcí a forem do bajtkódu virtuálního stroje Javy (všechny vyhodnocované formy jsou tedy nejprve interně přeloženy do bajtkódu a teprve poté je spuštěno jejich vyhodnocení).

Základní vlastnosti samotného interpretu jazyka Clojure jsou přitom odvozeny od interpretrů používaných ve většině variant programovacího jazyka LISP, což znamená, že autoři Clojure (resp. přesněji řečeno především jeho původní a dodnes nejaktivnější autor Rich Hickley) vychází z ověřených technologií, které byly poprvé implementovány již před více než padesáti roky v rámci vývoje LISPu a na něj navazujících jazyků (Scheme). Základem interpretru programovacího jazyka Clojure je, stejně jako v LISPu, smyčka nazývaná REPL (Read-Evaluate-Print-Loop), jejíž název vyplývá z toho, že mohla být relativně jednoduše implementována způsobem ukázaným pod tímto odstavcem. Ostatně z historického pohledu je zajímavé, že nějak podobně vlastně LISP vznikl, když si jeho autor (John McCarthy) uvědomil, že na základě implementace rekurzivní podoby funkce eval a několika dalších pomocných funkcí dokáže vytvořit plnohodnotný programovací jazyk:

(loop (print (eval (read))))

Poznámka: v programovacím jazyku Clojure by ve skutečnosti musel být tento „ortodoxní“ zápis proveden poněkud odlišným způsobem, protože zde se speciální forma loop musí zapisovat společně se speciální formou recur – loop totiž v Clojure slouží především pro označení bodu, kde začíná rekurze (a navíc i pro určení lokálních proměnných používaných uvnitř opakujícího se bloku kódu). Taktéž je nutné nahradit funkci print za println, takže v Clojure může implementace smyčky REPL vypadat například takto:

user=> (loop [] (println (eval (read))) (recur))
(+ 1 2)
3
(* 6 7)
42
(str "Hello world")
Hello world
^D
user=>

2. Smyčka REPL (Read-Eval-Print-Loop) a systém maker v tradičních LISPech

Jednoduchý interpret LISPu tedy mohl být teoreticky implementován pouze s využitím trojice funkcí read (načtení výrazu/formy ze standardního vstupu), print (tisk výsledku vyhodnocení výrazu/formy na standardní výstup), eval (většinou rekurzivně implementovaná funkce určená pro vyhodnocení načtené formy), které byly doplněny speciální formou či makrem loop (nekonečná smyčka – při striktním pohledu se v tomto případě nemůže jednat o funkci). Ve skutečnosti je však samozřejmě nutné, aby byl prakticky použitelný programovací jazyk doplněn o alespoň minimální množství základních funkcí a speciálních forem. V případě původního LISPu se jednalo o sedm funkcí a dvě speciální formy: atom, car, cdr, cond, cons, eq, quote, lambda a label.

Původně relativně velmi jednoduše a přitom elegantně implementovaný interpret programovacího jazyka LISP se postupně začal vyvíjet a jednou z nových a přitom mocných technik, které do něj byly přidány, jsou takzvaná makra, která se však v mnoha ohledech liší od maker používaných například v programovacích jazycích C a C++. Zatímco v céčku jsou makra zpracovávána poměrně „hloupým“ preprocesorem, který dokáže provádět textové substituce, načítat vkládané soubory a vyhodnocovat jednoduché podmínky, mohou makra implementovaná v jazyce LISP pracovat přímo se zadávanými formami, které makra mohou různým způsobem modifikovat – přitom se zde využívá faktu, že v LISPu a tudíž i v Clojure jsou programy reprezentovány ve formě (obvykle rekurzivně vnořených) seznamů, a změnou obsahu těchto seznamů lze vlastně přímo manipulovat s takzvaným abstraktním syntaktickým stromem (AST – Abstract Syntax Tree).

Není bez zajímavosti, že s AST se v LISP/Clojure může manipulovat za použití stejných mechanismů (funkcí/forem/maker), které se používají i při běžném programování – jinými slovy to znamená, že jazyk maker je stále jazykem, v němž se zapisují programy (na rozdíl od zmíněného céčka a C++, kde je jazyk maker zcela odlišný). Jinými slovy to znamená, že se při tvorbě maker musíme seznámit pouze se způsobem zápisu maker, ale v samotných makrech se mohou používat funkce, které jsme si již v tomto seriálu popsali – většinou se bude jednat o funkce pro práci se seznamy, což je vzhledem ke způsobu reprezentace programů (jako do sebe vnořených seznamů) pochopitelné.

3. První písmeno ve zkratce REPL: objekt Reader a jeho makra

Před popisem systému maker v programovacím jazyku Clojure si ještě musíme říci, že ve skutečnosti existují dva typy maker – takzvaná reader macros, neboli makra zabudovaná přímo do objektu/modulu, který načítá formy ze standardního vstupu a potom běžná makra. Nejprve se budeme zabývat makry používanými při načítání forem ze standardního vstupu. Důvodů, proč je lepší začít s popisem této skupiny maker je více, například fakt, že tato makra nelze vytvářet a existující makra nelze modifikovat (na rozdíl od Common Lispu, kde to možné je) a taktéž to, že se tato makra používají v prakticky všech zdrojových kódech, aniž by si vývojáři většinou uvědomovali, že ve svých programech nějaká makra používají :-) Důvod existence reader maker je jednoduchý – umožňují zkrácení zápisu programů, zajišťují možnost zápisu komentářů (ty totiž nejsou považovány za běžné formy, protože nevrací žádnou hodnotu, ani nil) a taktéž je možné s pomocí tohoto typu maker přidávat k symbolům, seznamům, vektorům, mapám atd. takzvaná metadata.

Zjednodušeně řečeno je možné říci, že reader makra pracují podobně jako preprocesor v programovacích jazycích C a C++, protože text zapisovaný či posílaný na standardní vstup je nejprve těmito makry zpracován a posléze je – stále v textové podobě – poslán funkci read. Tato makra tedy slouží pro provádění „pouhých“ textových substitucí a nikoli k modifikaci AST, jak je tomu u běžných maker. Která reader makra jsou v Clojure podporována, nám prozradí následující tabulka, z níž je patrné, že některá makra provádí skutečně značně jednoduchou činnost, například pouhé odstranění komentářů:

# Makro Název Kapitola Význam
1 ; comment 4 umožňuje obejít zápis (comment nějaký text) u komentářů
2 \ character 4 používané při zápisu znakových literálů
3 ^ metadata 4 přidání metadat k symbolům, seznamům, vektorům, mapám a množinám
4 ' quote 6 nahrazuje zápis (quote …)
5 ` syntax-quote 6 provádí plnou kvalifikaci symbolů + zde lze použít makra ~ a ~@
6 ~ unquote 7 zajistí, že se vyhodnotí pouze označená část formy (= provede substituci této části výsledkem)
7 ~@ unquote-splicing 7 podobné předchozími makru, ovšem výsledná sekvence se vloží ve formě samostatných prvků do „obalující“ sekvence
8 @ deref 5 nahrazuje zápis (deref …)
9 # dispatch 8 má různé funkce: donutí reader, aby použil makro z jiné tabulky maker

Význam všech maker zmíněných v předchozí tabulce si podrobněji vysvětlíme v navazujících kapitolách.

4. Makra „comment“, „character“ a „metadata“

S popisem maker vestavěných do objektu Reader začneme jen pozvolna, napřed si totiž popíšeme ta nejjednodušší makra, která jsme si navíc již ukázali v předchozích částech tohoto seriálu. Nejjednodušším reader makrem je s velkou pravděpodobností makro nazvané „comment“ zapisované pomocí znaku ; (středník). Veškerý text, který je zapsaný mezi středníkem a koncem řádku je ignorován, takže toto makro lze použít pro jednoduchý zápis jednořádkových komentářů. Pokud by toto reader makro neexistovalo, muselo by se pro zápis komentářů namísto toho používat normální makro comment, které se však musí zapisovat stejně, jako jakákoli jiná forma, tj. i s kulatými závorkami, což na čitelnosti komentářů určitě nepřidá. Nicméně makro comment má svou nezastupitelnou úlohu, protože pomocí něho můžeme do komentáře „uzavřít“ i delší část kódu – ostatně toto makro je použito i v samotných zdrojových kódech jazyka Clojure. Podívejme se na několik jednoduchých demonstračních příkladů:

; jednořádkové komentáře
user=> ; toto je komentar
user=> ; ignorovany v REPL\
user=>
 
; víceřádkový komentář
user=> (comment
komentar
muze
mit
nekolik
radku)
; povšimněte si, že (comment) vrací hodnotu nil
nil
 
user=>

Další velmi často používané reader makro se zapisuje pomocí znaku \ (zpětné lomítko). Toto makro slouží pro zápis znakových konstant (literálů) do zdrojového kódu. Tisknutelné znaky je možné zapsat buď přímo za zpětné lomítko, nebo je možné použít kód libovolného znaku z×Unicode, přičemž kód tohoto znaku musí být zapsán v hexadecimální soustavě za dvojici znaků „\u“:

; běžný tisknutelný znak:
user=> \a
\a
 
; další běžný tisknutelný znak:
user=> \1
\1
 
; znak "u":
user=> \u
\u
; znak s hexadecimální hodnotou 0x40, neboli 64:
 
user=> \u0040
\@
 
; pokus o zápis neplatného znakového literálu:
user=> \aa
RuntimeException Unsupported character: \aa  clojure.lang.Util.runtimeException (Util.java:170)
 
user=>

Posledním „jednoduchým“ reader makrem je makro zapisované pomocí znaku ^. Toto makro slouží k přiřazení metadat k symbolu, seznamu, vektoru, množině či mapě. S metadaty jsme se již okrajově setkali v předchozí části tohoto seriálu a budeme se jim ještě věnovat příště. Dnes si pouze ukážeme základní způsob využití makra ^:

; nastavení metadat k vektoru
user=> ^{:atribut1 "Hodnota1" :atribut2 "Hodnota2"} [1 2 3]
[1 2 3]
 
; uložení vektoru a k němu přiřazeným metadatům
; do proměnné vektor
user=> (def vektor ^{:atribut1 "Hodnota1" :atribut2 "Hodnota2"} [1 2 3])
#'user/vektor
 
; přečtení stavu proměnné vektor
user=> vektor
[1 2 3]
 
; přečtení metadat přiřazených k proměnné vektor
user=> (meta vektor)
{:atribut2 "Hodnota2", :atribut1 "Hodnota1"}
 
user=>

Navíc existuje i alternativní způsob použití makra ^, který slouží pro naplnění jediného atributu s názvem :tag:

; uložení vektoru a k němu přiřazeným metadatům
; do proměnné vektor
user=> (def vektor ^"xyzzy" [1 2 3])
#'user/vektor
 
; přečtení stavu proměnné vektor
user=> vektor
[1 2 3]
 
; přečtení metadat přiřazených k proměnné vektor
user=> (meta vektor)
{:tag "xyzzy"}
 
user=>

5. Makro „deref“

S makrem objektu reader, které se nazývá „deref“, jsme se již ve skutečnosti v tomto seriálu setkali, a to dokonce na několika místech. Toto makro totiž může být použito pro přečtení hodnoty, přesněji řečeno stavu reference a používá se u mnoha typů referencí (referenčních typů):

# Referenční typ
1 @ref
2 @agent
3 @var
4 @atom
5 @delay
6 @future
7 @promise

Chování makra @ se však u různých typů referencí liší. Zatímco @ref, @var či @atom pouze vrátí aktuální stav reference, v případě použití @future, @promise či @agent se ve skutečnosti musí počkat na dokončení výpočtu, který běží v jiném vláknu. Pro jistotu si připomeňme tabulku s funkcemi a makry použitými při práci s nejdůležitějšími referenčními typy:

Typ Var Ref Atom Agent
Vytvoření (def name value) (ref value) (atom value) (agent value)
Nastavení hodnoty (set! name value) (ref-set ref value) (reset! atom value) ×
Aplikace funkce × (alter ref funkce) (swap! atom funkce) (send agent funkce)
Čtení hodnoty name @ref @atom @agent

Demonstrační příklad na použití referencí typu ref, tedy „normálních“ proměnných platných v rámci aktuálního jmenného prostoru (ve skutečnosti je s referencemi typu ref možné provádět mnoho operací, které by s běžnými proměnnými nebyly možné):

; vytvoření refu
user=> (def my-ref (ref 42))
#'user/my-ref
 
; vytvoření refu
user=> (def string-ref (ref "Hello world"))
#'user/string-ref
 
; přečtení refu pomocí makra @
user=> @my-ref
42
 
; přečtení refu pomocí makra @
user> @string-ref
"Hello world"
 
user=>

Demonstrační příklad na použití atomů a samozřejmě i makra @:

; vytvoření nového atomu
user=> (def x (atom 42))
#'user/x
 
; globální symbol x je navázán
; na atom a nikoli na stav identity
; (=hodnotu)
user=> x
#<Atom@61a907: 42>
 
; pro získání aktuálního stavu
; je nutné použít dereferenci
user=> (deref x)
42
 
; namísto (deref x) se používá
; makro preprocesoru @
user=> @x
42
 
 
; atomická změna stavu identity
user=> (reset! x 10)
10
 
user=> (reset! x (+ 1 2 3))
6
 
user=> @x
7
 
 
; další možnost atomické změny
; stavu identity - nyní přes funkci
; aplikovanou na atom a popř. i další
; parametry
user=> (swap! x + 1)
7
 
user=> @x
7
 
user=>

Příklad vytvoření agenta, poslání funkce agentovi a čekání na dokončení výpočtu právě s použitím makra @:

; vytvoření agenta
user=> (def my-agent (agent 0))
#'user/my-agent
 
; poslání funkce agentovi (6 je druhý parametr funkce)
user=> (send my-agent + 6)
#<Agent@18622f3: 0>
 
; poslání funkce agentovi (7 je druhý parametr funkce)
user=> (send my-agent * 7)
#<Agent@18622f3: 6>
 
; dereference - získání nového stavu - pomocí makra @
user=> @my-agent
42
 
user=>

Další příklad s makrem @, tentokrát použitým pro objekty future:

; vytvoření objektu typu future a spuštění paralelního vlákna
user=> (def future_fibonacci1 (future (fibonacci 35)))
#'user/future_fibonacci1
 
; vytvoření dalšího objektu typu future a spuštění paralelního vlákna
user=> (def future_fibonacci2 (future (fibonacci 35)))
#'user/future_fibonacci2
 
; čekání na dokončení prvního paralelně běžícího výpočtu
; - zde se využívá makro @
user=> @future_fibonacci1
9227465
 
; čekání na dokončení druhého paralelně běžícího výpočtu
; - zde se využívá makro @
user=> @future_fibonacci2
9227465
 
user=>

Poslední demonstrační příklad: makro @ a objekty typu promise:

; vytvoření objektu typu promise
; a navázání na symbol promise-test
user=> (def promise-test (promise))
#'user/promise-test
 
; nastavení hodnoty
user=> (deliver promise-test 42)
#<core$promise$reify__6153@1318b: 42>
 
; získání nastavené hodnoty pomocí makra @
user=> @promise-test
42
 
user=>

6. Makra „quote“ a „syntax-quote“

Konečně se dostáváme k zajímavějším a užitečnějším makrům. Jedno z nejdůležitějších a nejčastěji používaných maker se jmenuje „quote“ a zapisuje se pomocí apostrofu. Toto makro zakazuje vyhodnocování seznamů, protože pokud by objekt reader načetl formu ve tvaru (a b c), předal by ji do funkce eval, kde by se tato forma vyhodnotila jako volání funkce a s parametry b a c. Pokud však reader načte formu '(a b c), ztransformuje ji do tvaru (quote (a b c)), přičemž quote je speciální forma zakazující vyhodnocení. Na většinu ostatních objektů kromě seznamů nemá makro „quote“ většinou žádný vliv:

; zde nemá quote žádný vliv
user=> '42
42
 
; zákaz vyhodnocení seznamů jako funkce
user=> '(1 2 3)
(1 2 3)
 
; zde nemá quote žádný vliv
user=> '[1 2 3]
[1 2 3]
 
; stejné jako předchozí forma
user=> [1 2 3]
[1 2 3]
 
; zákaz vyhodnocení seznamů jako funkce
user=> '(* 6 7)
(* 6 7)
 
; zde se však seznam vyhodnotí jako funkce *
user=> (* 6 7)
42
 
user=>

Kromě makra „quote“ ještě objekt reader rozeznává poněkud komplikovanější makro nazývané „syntax-quote“, které se zapisuje pomocí zpětného apostrofu: `. Chování tohoto makra se liší podle toho, s jakým typem objektu je použito, ovšem ve všech případech se makro chová tak, aby nedocházelo k vyhodnocení jeho argumentů, popř. ani k vyhodnocení vnořených forem. V následujících příkladech dochází k jednoduchému zákazu vyhodnocení předané formy:

user=> `42
42
 
user=> `(1 2 3)
(1 2 3)
 
user=> `[1 2 3]
[1 2 3]
 
user=>

Dále toho makro dokáže nahradit zjednodušené jméno symbolu jeho plně kvalifikovaným jménem. Nejlépe si to opět ukážeme na několika příkladech:

user=> `seq
clojure.core/seq
 
user=> `map
clojure.core/map
 
user=> `Integer/valueOf
java.lang.Integer/valueOf
 
user=>

Zákaz vyhodnocení a současně i náhrada zjednodušeného jména symbolu na plně kvalifikované jméno se projeví tím, že se následující seznamy (a vektor na konci) nevyhodnotí jako funkce, ale například funkce * se nahradí plným jménem:

user=> `(* 6 7)
(clojure.core/* 6 7)
 
user=> `(str "Hello" "world")
(clojure.core/str "Hello" "world")
 
user=> `[* seq str xyzzy neznamy]
[clojure.core/* clojure.core/seq clojure.core/str user/xyzzy user/neznamy]
 
user=>

Toto makro se poměrně často používá při tvorbě uživatelských maker, což si příště ukážeme na demonstračních příkladech.

7. Makra „unquote“ a „unquote-splicing“

Makro nazvané „unquote“, které se zapisuje pomocí znaku ~ (tilda) dokáže vynutit vyhodnocení určité části výrazu, a to tehdy, pokud je tento výraz umístěn v makru ` (syntax-quote), nikoli však ' (quote). Nejprve si ukažme způsob zápisu tohoto makra i to, jaký má toto makro vliv na zapisované výrazy:

; makro quote zakáže vyhodnocení celého seznamu
user=> '(1 2 (* 6 7) (/ 4 2))
(1 2 (* 6 7) (/ 4 2))
 
; makro syntax-quote zakáže vyhodnocení taktéž a
; současně provede náhradu jmen funkcí za jejich plný tvar
user=> `(1 2 (* 6 7) (/ 4 2))
(1 2 (clojure.core/* 6 7) (clojure.core// 4 2))
 
; pomocí ~ vynutíme vyhodnocení podvýrazu (* 6 7)
user=> `(1 2 ~(* 6 7) (/ 4 2))
(1 2 42 (clojure.core// 4 2))
 
; pomocí ~ vynutíme vyhodnocení podvýrazu (/ 4 2)
user=> `(1 2 (* 6 7) ~(/ 4 2))
(1 2 (clojure.core/* 6 7) 2)
 
; pomocí dvou ~ vynutíme vyhodnocení obou podvýrazů
user=> `(1 2 ~(* 6 7) ~(/ 4 2))
(1 2 42 2)
 
user=>

Podobným způsobem pracuje i makro ~@, ovšem to navíc ještě provádí „zplošťování seznamů“. Prozatím si chování tohoto makra ukážeme na velmi jednoduchém umělém příkladu, ovšem příště při popisu uživatelských maker uvidíme, jak může být ~@ ve skutečnosti užitečné:

; uživatelsky definovaný seznam
user=> (def s '(1 2 3))
#'user/s
 
; makro quote zcela zakáže vyhodnocování
user=> '(1 2 3 (cons s s))
(1 2 3 (cons s s))
 
; makro syntax-quote taktéž, ovšem ještě nahradí
; všechny symboly jejich plnými jmény
user=> `(1 2 3 (cons s s))
(1 2 3 (clojure.core/cons user/s user/s))
 
; vynutíme si vyhodnocení podvýrazu (cons s s)
; který vrací ((1 2 3) 1 2 3)
user=> `(1 2 3 ~(cons s s))
(1 2 3 ((1 2 3) 1 2 3))
 
; dtto, ovšem seznam, jenž je výsledkem (cons s s)
; je zploštěn (jakoby je
odstraněna jedna úroveň zanoření)
user=> `(1 2 3 ~@(cons s s))
(1 2 3 (1 2 3) 1 2 3)
 
user=>

8. Makro „dispatch“

Posledním typem makra používaného interně v objektu reader, které si v dnešním článku popíšeme, je makro nazvané „dispatch“ jenž je ve zdrojovém textu zapisováno pomocí znaku # (křížek, hash symbol). Pokud reader narazí ve vstupním textu na tento znak, ví, že má na základě těsně následujícího znaku vybrat určité makro z alternativní tabulky maker. To ve skutečnosti znamená, že znak # musí být následován dalším znakem se speciálním významem. O jaký znak se jedná se dozvíme v tabulce zobrazené pod tímto odstavcem:

# Dvojice znaků Způsob použití Význam
1 #{ #{prvky} zápis množiny
2 #" #„regexp-pattern“ zápis regulárního výrazu
3 #' #'var quotování proměnných
4 #( #(telo funkce) zkrácený zápis anonymní funkce
5 #_ #_text text je ignorován – alternativní způsob komentáře

Některé zápisy uvedené v předchozí tabulce již známe, takže si je probereme pouze krátce. Asi nejjednodušší je zápis množiny pomocí makra #{, za nímž následují prvky množiny uzavřené pravou složenou závorkou. Důvod, proč je množina zapsána takto prapodivně je velmi jednoduchý – pro množiny již nezbyly k dispozici žádné vhodné závorky, protože kulaté závorky jsou již z historických důvodů vyhrazeny pro seznamy, hranaté závorky se celkem logicky použily pro zápis vektorů a složené závorky (bez # na začátku) jsou použity pro mapy, které jsou přece jen využívány častěji než množiny (ještě by sice bylo možné použít znaky < a >, to by však kolidovalo s jejich standardním významem.

Další typ makra začíná dvojicí znaků #" a končí znakem " (uvozovky). Mezi obě uvozovky se zapisují regulární výrazy, které jsou využívány některými funkcemi, například re-seq, re-find, re-groups a re-matches. Regulární výraz uvedený v makru #" je zkontrolován a následně zkompilován ihned v čase svého načítání, na rozdíl od regulárního výrazu zadaného s využitím funkce re-pattern: v tomto případě se totiž kontrola a kompilace provede až ve chvíli, kdy je tato funkce zpracována a vyhodnocena. Více podrobností o využití regulárních výrazů si řekneme v navazujících částech tohoto seriálu, zde si jen ukážeme základy práce s nimi:

; nalezení prvního podřetězce vyhovujícímu regulárnímu výrazu
user=> (re-find #"[0-9]+" "123qwe456asd")
"123"
 
; nalezení podřetězců vyhovujících zadanému regulárnímu výrazu
user=> (re-seq #"[0-9]+" "123qwe456asd")
("123" "456")
 
; uložení výsledku do proměnné result
user=> (def result (re-seq #"[0-9]+" "123qwe456asd"))
#'user/result
 
; získání prvního podřetězce
user=> (first result)
"123"
 
; získání druhého podřetězce
user=> (second result)
"456"
 
user=>

Dalším makrem objektu reader, které začíná znakem #, je makro #(. Toto makro slouží ke zkrácenému zápisu anonymní funkce. Za kulatou závorkou je přímo zapsáno tělo anonymní funkce bez explicitního uvedení jmen parametrů. Tento zápis se transformuje do volání fn ještě v preprocesoru smyčky REPL, tj. samotná smyčka REPL již „vidí“ pouze zápis speciální formy fn). Zápis vypadá následovně:

#(tělo anonymní funkce)

To, že chybí jméno funkce je pochopitelné – kdyby funkce měla jméno, už by nebyla funkcí anonymní :-) ovšem absence jmen parametrů možná může být poněkud matoucí, protože anonymní funkce bez parametrů by vlastně odpovídala vrácení konstanty (popř. nefunkcionálním „šílenostem“ typu random :-). Ve skutečnosti však i anonymní funkce vytvořená pomocí znaku # parametry mít může. Ty jsou pojmenovány v závislosti na své pozici takto: %1, %2 atd. Předpokládá se totiž, že anonymní funkce budou velmi krátké a s malým množstvím parametrů – pokud by tomu tak nebylo, nic programátorům nebrání vrátit se ke speciální formě fn.

Ukažme si nějaké příklady použití vytvoření anonymních funkcí pomocí znaku #.

Aplikace anonymní funkce vytvořené s využitím speciální formy fn:

((fn [x y] (* x y)) 6 7)

Zkrácený (ekvivalentní) způsob zápisu:

(#(* %1 %2) 6 7)

Anonymní funkce se velmi často používají jako parametry funkcí map, pmap, filter apod.:

widgety

(map #(/ 2 %1) [1 2 3 4 5 6])
(2 1 2/3 1/2 2/5 1/3)

Popř.:

(map #(/ 2 %1) (range 1 7))
(2 1 2/3 1/2 2/5 1/3)

Zbylá makra začínající křížkem si popíšeme příště.

9. Odkazy na Internetu

  1. Tech behind Tech: Clojure Macros Simplified
    http://techbehindtech.com/2010/09/28/clo­jure-macros-simplified/
  2. Fatvat – Exploring functional programming: Clojure Macros
    http://www.fatvat.co.uk/2009/02/clo­jure-macros.html
  3. Eulerovo číslo
    http://cs.wikipedia.org/wi­ki/Eulerovo_číslo
  4. List comprehension
    http://en.wikipedia.org/wi­ki/List_comprehension
  5. List Comprehensions in Clojure
    http://asymmetrical-view.com/2008/11/18/list-comprehensions-in-clojure.html
  6. Clojure Programming Concepts: List Comprehension
    http://en.wikibooks.org/wi­ki/Clojure_Programming/Con­cepts#List_Comprehension
  7. Clojure core API: for macro
    http://clojure.github.com/clo­jure/clojure.core-api.html#clojure.core/for
  8. cirrus machina – The Clojure for macro
    http://www.cirrusmachina.com/blog/com­ment/the-clojure-for-macro/
  9. Clojure.org: Clojure home page
    http://clojure.org/downloads
  10. Clojure.org: Vars and the Global Environment
    http://clojure.org/Vars
  11. Clojure.org: Refs and Transactions
    http://clojure.org/Refs
  12. Clojure.org: Atoms
    http://clojure.org/Atoms
  13. Clojure.org: Agents as Asynchronous Actions
    http://clojure.org/agents
  14. A Couple of Clojure Agent Examples
    http://lethain.com/a-couple-of-clojure-agent-examples/
  15. Clojure – Functional Programming for the JVM
    http://java.ociweb.com/mar­k/clojure/article.html
  16. Clojure quick reference
    http://faustus.webatu.com/clj-quick-ref.html
  17. 4Clojure
    http://www.4clojure.com/
  18. ClojureDoc
    http://clojuredocs.org/
  19. Clojure (Wikipedia EN)
    http://en.wikipedia.org/wiki/Clojure
  20. Clojure (Wikipedia CS)
    http://cs.wikipedia.org/wiki/Clojure
  21. Riastradh's Lisp Style Rules
    http://mumble.net/~campbe­ll/scheme/style.txt
  22. Dynamic Languages Strike Back
    http://steve-yegge.blogspot.cz/2008/05/dynamic-languages-strike-back.html
  23. Scripting: Higher Level Programming for the 21st Century
    http://www.tcl.tk/doc/scripting.html
  24. Java Virtual Machine Support for Non-Java Languages
    http://docs.oracle.com/ja­vase/7/docs/technotes/gui­des/vm/multiple-language-support.html
  25. New JDK 7 Feature: Support for Dynamically Typed Languages in the Java Virtual Machine
    http://java.sun.com/develo­per/technicalArticles/Dyn­TypeLang/
  26. JSR 223: Scripting for the JavaTM Platform
    http://jcp.org/en/jsr/detail?id=223
  27. JSR 292: Supporting Dynamically Typed Languages on the JavaTM Platform
    http://jcp.org/en/jsr/detail?id=292
  28. Java 7: A complete invokedynamic example
    http://niklasschlimm.blog­spot.com/2012/02/java-7-complete-invokedynamic-example.html
  29. InvokeDynamic: Actually Useful?
    http://blog.headius.com/2007/01/in­vokedynamic-actually-useful.html
  30. A First Taste of InvokeDynamic
    http://blog.headius.com/2008/09/first-taste-of-invokedynamic.html
  31. Java 6 try/finally compilation without jsr/ret
    http://cliffhacks.blogspot­.com/2008/02/java-6-tryfinally-compilation-without.html
  32. An empirical study of Java bytecode programs
    http://www.mendeley.com/research/an-empirical-study-of-java-bytecode-programs/
  33. Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
    http://www.mobilefish.com/tu­torials/java/java_quickgu­ide_jvm_instruction_set.html
  34. The JVM Instruction Set
    http://mpdeboer.home.xs4a­ll.nl/scriptie/node14.html
  35. Control Flow in the Java Virtual Machine
    http://www.artima.com/under­thehood/flowP.html
  36. Root.cz: Využití komprimovaných ukazatelů na objekty v JVM
    http://www.root.cz/clanky/vyuziti-komprimovanych-ukazatelu-na-objekty-v-nbsp-jvm/
  37. Root.cz: JamVM aneb alternativa k HotSpotu nejenom pro embedded zařízení a chytré telefony
    http://www.root.cz/clanky/jamvm-aneb-alternativa-k-hotspotu-nejenom-pro-embedded-zarizeni-tablety-a-chytre-telefony/
  38. The JavaTM Virtual Machine Specification, Second Edition
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/VMSpec­TOC.doc.html
  39. The class File Format
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/Clas­sFile.doc.html
  40. javap – The Java Class File Disassembler
    http://docs.oracle.com/ja­vase/1.4.2/docs/tooldocs/win­dows/javap.html
  41. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.die.net/man/1/javap-java-1.6.0-openjdk
  42. Using javap
    http://www.idevelopment.in­fo/data/Programming/java/mis­cellaneous_java/Using_javap­.html
  43. Examine class files with the javap command
    http://www.techrepublic.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  44. BCEL Home page
    http://commons.apache.org/bcel/
  45. BCEL Manual
    http://commons.apache.org/bcel/ma­nual.html
  46. Byte Code Engineering Library (Wikipedia)
    http://en.wikipedia.org/wiki/BCEL
  47. Java programming dynamics, Part 7: Bytecode engineering with BCEL
    http://www.ibm.com/develo­perworks/java/library/j-dyn0414/
  48. Bytecode Engineering
    http://book.chinaunix.net/spe­cial/ebook/Core_Java2_Volu­me2AF/0131118269/ch13lev1sec6­.html
  49. BCEL Tutorial
    http://www.smfsupport.com/sup­port/java/bcel-tutorial!/
  50. ASM Home page
    http://asm.ow2.org/
  51. Seznam nástrojů využívajících projekt ASM
    http://asm.ow2.org/users.html
  52. ObjectWeb ASM (Wikipedia)
    http://en.wikipedia.org/wi­ki/ObjectWeb_ASM
  53. Java Bytecode BCEL vs ASM
    http://james.onegoodcooki­e.com/2005/10/26/java-bytecode-bcel-vs-asm/
  54. Bytecode Outline plugin for Eclipse (screenshoty + info)
    http://asm.ow2.org/eclipse/index.html
  55. aspectj (Eclipse)
    http://www.eclipse.org/aspectj/
  56. Aspect-oriented programming (Wikipedia)
    http://en.wikipedia.org/wi­ki/Aspect_oriented_program­ming
  57. AspectJ (Wikipedia)
    http://en.wikipedia.org/wiki/AspectJ
  58. EMMA: a free Java code coverage tool
    http://emma.sourceforge.net/
  59. Cobertura
    http://cobertura.sourceforge.net/
  60. FindBugs
    http://findbugs.sourceforge.net/
  61. GNU Classpath
    www.gnu.org/s/classpath/
  62. Java VMs Compared
    http://bugblogger.com/java-vms-compared-160/
  63. JSRs: Java Specification Requests – JSR 223: Scripting for the Java Platform
    http://www.jcp.org/en/jsr/de­tail?id=223
  64. Scripting for the Java Platform
    http://java.sun.com/develo­per/technicalArticles/J2SE/Des­ktop/scripting/
  65. Scripting for the Java Platform (Wikipedia)
    http://en.wikipedia.org/wi­ki/Scripting_for_the_Java_Plat­form
  66. Java Community Process
    http://en.wikipedia.org/wi­ki/Java_Specification_Requ­est
  67. Java HotSpot VM Options
    http://www.oracle.com/technet­work/java/javase/tech/vmop­tions-jsp-140102.html
  68. Great Computer Language Shootout
    http://c2.com/cgi/wiki?Gre­atComputerLanguageShootout
  69. Java performance
    http://en.wikipedia.org/wi­ki/Java_performance
  70. Trying the prototype
    http://mail.openjdk.java.net/pi­permail/lambda-dev/2010-August/002179.html
  71. Better closures (for Java)
    http://blogs.sun.com/jrose/en­try/better_closures
  72. Lambdas in Java: An In-Depth Analysis
    http://www.infoq.com/articles/lambdas-java-analysis
  73. Class ReflectiveOperationException
    http://download.java.net/jdk7/doc­s/api/java/lang/Reflective­OperationException.html
  74. Scala Programming Language
    http://www.scala-lang.org/
  75. Run Scala in Apache Tomcat in 10 minutes
    http://www.softwaresecret­weapons.com/jspwiki/run-scala-in-apache-tomcat-in-10-minutes
  76. Fast Web Development With Scala
    http://chasethedevil.blog­spot.cz/2007/09/fast-web-development-with-scala.html
  77. Top five scripting languages on the JVM
    http://www.infoworld.com/d/developer-world/top-five-scripting-languages-the-jvm-855
  78. Proposal: Indexing access syntax for Lists and Maps
    http://mail.openjdk.java.net/pi­permail/coin-dev/2009-March/001108.html
  79. Proposal: Elvis and Other Null-Safe Operators
    http://mail.openjdk.java.net/pi­permail/coin-dev/2009-March/000047.html
  80. Java 7 : Oracle pushes a first version of closures
    http://www.baptiste-wicht.com/2010/05/oracle-pushes-a-first-version-of-closures/
  81. Groovy: An agile dynamic language for the Java Platform
    http://groovy.codehaus.org/Operators
  82. Better Strategies for Null Handling in Java
    http://www.slideshare.net/Step­han.Schmidt/better-strategies-for-null-handling-in-java
  83. Control Flow in the Java Virtual Machine
    http://www.artima.com/under­thehood/flowP.html
  84. Java Virtual Machine
    http://en.wikipedia.org/wi­ki/Java_virtual_machine
  85. ==, .equals(), compareTo(), and compare()
    http://leepoint.net/notes-java/data/expressions/22com­pareobjects.html
  86. New JDK7 features
    http://openjdk.java.net/pro­jects/jdk7/features/
  87. Project Coin: Bringing it to a Close(able)
    http://blogs.sun.com/darcy/en­try/project_coin_bring_clo­se
  88. CloseableFinder source code
    http://blogs.sun.com/darcy/re­source/ProjectCoin/Closea­bleFinder.java
  89. Joe Darcy blog about JDK
    http://blogs.sun.com/darcy
  90. Java 7 – more dynamics
    http://www.baptiste-wicht.com/2010/04/java-7-more-dynamics/
  91. New JDK 7 Feature: Support for Dynamically Typed Languages in the Java Virtual Machine
    http://java.sun.com/develo­per/technicalArticles/Dyn­TypeLang/index.html
Našli jste v článku chybu?
Podnikatel.cz: Letáky? Lidi zuří, ale ony stále fungují

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

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

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

Lupa.cz: Kdo vyhraje Kříšťálovou Lupu? Hlasování začalo!

Kdo vyhraje Kříšťálovou Lupu? Hlasování začalo!

Lupa.cz: Cimrman má hry na YouTube i vlastní doodle

Cimrman má hry na YouTube i vlastní doodle

DigiZone.cz: Světový pohár v přímém přenosu na ČT

Světový pohár v přímém přenosu na ČT

Lupa.cz: Jak levné procesory změnily svět?

Jak levné procesory změnily svět?

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

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

Lupa.cz: Další Češi si nechali vložit do těla čip

Další Češi si nechali vložit do těla čip

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

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

DigiZone.cz: DVB-T2 ověřeno: seznam TV zveřejněn

DVB-T2 ověřeno: seznam TV zveřejněn

DigiZone.cz: Mordparta: trochu podchlazený 87. revír

Mordparta: trochu podchlazený 87. revír

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

dTest odhalil ten nejlepší kečup

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

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

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

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

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

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

Podnikatel.cz: Nemá dluhy? Zjistíte to na poště

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

Podnikatel.cz: Instalatér, malíř a elektrikář. "Vymřou"?

Instalatér, malíř a elektrikář. "Vymřou"?

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

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

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

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

Podnikatel.cz: Udělali jsme velkou chybu, napsal Čupr

Udělali jsme velkou chybu, napsal Čupr