Hlavní navigace

Babashka: interpret Clojure určený pro rychlé spouštění utilit z příkazového řádku

Jazyk Clojure existuje ve třech oficiálně podporovaných variantách. Jednak pro JVM, dále pak o variantu pro CLR a nakonec existuje ClojureScript. Ovšem žádná z těchto variant se nehodí pro psaní utilit pro příkazový řádek.
Pavel Tišnovský 13. 8. 2020
Doba čtení: 36 minut

Sdílet

11. Zpracování dat vracených ve formátu JSON

12. Ukázka síly jazyka Clojure – threading makro

13. Využití dalších vlastností Clojure – výpočty s neomezeným rozsahem hodnot

14. Datový typ double a počítání se zlomky

15. Výpočty ve více vláknech – map versus pmap

16. Porovnání rychlosti výpočtů Babashky s implementací Clojure pro JVM

17. Zobrazení nápovědy přímo v interaktivní smyčce REPL

18. Repositář s demonstračními příklady

19. Odkazy na předchozí části seriálu o programovacím jazyku Clojure

20. Odkazy na Internetu

1. Babashka – interpret Clojure určený pro rychlé spouštění utilit z příkazového řádku

S programovacím jazykem Clojure jsme se již na stránkách Roota mnohokrát setkali. Popisovali jsme si jak interpret a překladač běžící nad JVM, tak i (i když ve stručnosti) ClojureScript, tedy transpřekladač z Clojure do JavaScriptu. Tyto dvě implementace Clojure mají jednu společnou (a dosti nepěknou) vlastnost – spuštění skriptů, popř. spuštění interaktivní smyčky REPL je dosti zdlouhavé a i na nových výkonných počítačích může trvat několik sekund. To sice nevadí při delší práci (představme si programátora, který má REPL otevřený celý pracovní den), ovšem i z tohoto důvodu se Clojure nepoužívá pro psaní skriptů, které mají být rychle spouštěny z příkazové řádky. Taktéž delší inicializace REPLu není příliš dobrým „reklamním materiálem“ tohoto jazyka, i když výsledné aplikace přeložené do bajtkódu a JITované v JVM mají většinou velmi dobrou výkonnost.

Řešení tohoto problému spočívá v použití nástroje nazvaného Babashka. Jedná se o interpret Clojure naprogramovaný taktéž v Clojure (což není ve světě LISPu nic neobvyklého). Ovšem tento interpret je přeložen do nativního kódu s využitím GraalVM, takže výsledkem je spustitelný (binární) soubor. Důležité je, že je spustitelný prakticky okamžitě, takže REPL či interpretace skriptů spouštěných z příkazové řádky či z BASHe je již velmi dobře možná. Navíc je Babashka dodávána „včetně baterií“, což znamená, že holý interpret je doplněn mnoha užitečnými balíčky – viz též sedmou kapitolu.

2. Instalace Babashky a první otestování základní funkcionality

Instalace Babashky je popsána na stránce https://github.com/borkdu­de/babashka#quickstart. Nejprve je nutné stáhnout krátký instalační skript:

$ curl -s https://raw.githubusercontent.com/borkdude/babashka/master/install -o install-babashka

Nyní je vhodné si prohlédnout, co skript dělá, protože ho budeme spouštět s právy roota:

$ chmod +x install-babashka && sudo ./install-babashka

Výsledný spustitelný soubor by měl být dostupný v podadresáři /usr/local/bin, o čemž se můžeme snadno přesvědčit:

$ ls -l /usr/local/bin

Stažený a nainstalovaný nástroj Babashka je zobrazen na prvním místě:

total 184948
-rwxr-xr-x. 1 root root  67231896 Jun 27 13:39 bb
-rwxr-xr-x. 1 root root       218 Oct  9  2019 flake8
-rwxr-xr-x. 1 root root       384 Oct  9  2019 futurize
-rwxr-xr-x. 1 root root 120350344 Sep 27  2019 oc
-rwxr-xr-x. 1 root root       388 Oct  9  2019 pasteurize
-rwxr-xr-x. 1 root root       216 Oct  9  2019 pycodestyle
-rwxr-xr-x. 1 root root       215 Oct  8  2019 pyflakes
-rwxr-xr-x. 1 root root       208 Oct  9  2019 radon
-rwxr-xr-x. 1 root root       213 Oct  3  2019 virtualenv
-rwxr-xr-x. 1 root root       215 Oct  8  2019 vulture
-rwxr-xr-x. 1 root root   1750603 Mar 23 21:15 youtube-dl
Poznámka: alternativně je možné stažení provést ručně a spustitelný soubor umístit například do ~/bin nebo /opt/bin, a to bez nutnosti použití rootovských práv.

Kontrola, zda je interpret skutečně spustitelný:

$ bb --help
 
Babashka v0.1.3
 
Options must appear in the order of groups mentioned below.
 
Help:
 
  --help, -h or -?    Print this help text.
  --version           Print the current version of babashka.
  --describe          Print an EDN map with information about this version of babashka.
 
In- and output flags:
 
  -i                  Bind *input* to a lazy seq of lines from stdin.
  -I                  Bind *input* to a lazy seq of EDN values from stdin.
  -o                  Write lines to stdout.
  -O                  Write EDN values to stdout.
  --stream            Stream over lines or EDN values from stdin. Combined with -i or -I *input* becomes a single value per iteration.
 
Uberscript:
 
  --uberscript <file> Collect preloads, -e, -f and -m and all required namespaces from the classpath into a single executable file.
 
Evaluation:
 
  -e, --eval <expr>   Evaluate an expression.
  -f, --file <path>   Evaluate a file.
  -cp, --classpath    Classpath to use.
  -m, --main <ns>     Call the -main function from namespace with args.
  --verbose           Print entire stacktrace in case of exception.
 
REPL:
 
  --repl              Start REPL. Use rlwrap for history.
  --socket-repl       Start socket REPL. Specify port (e.g. 1666) or host and port separated by colon (e.g. 127.0.0.1:1666).
  --nrepl-server      Start nREPL server. Specify port (e.g. 1667) or host and port separated by colon (e.g. 127.0.0.1:1667).
 
If neither -e, -f, or --socket-repl are specified, then the first argument that
is not parsed as a option is treated as a file if it exists, or as an
expression otherwise. Everything after that is bound to *command-line-args*.
Use -- to separate script command lin args from bb command line args.

Můžeme si vyzkoušet i interaktivní smyčku REPL:

$ bb
 
Babashka v0.1.3 REPL.
Use :repl/quit or :repl/exit to quit the REPL.
Clojure rocks, Bash reaches.
 
user=>
Poznámka: na rozdíl od REPL dostupného přes Leiningen by se měl REPL Babashky spustit prakticky okamžitě.

Poslední jednoduchý test – pokusíme se interpretovat následující skript:

(println "Hello" "world")

Spuštění interpretru:

$ bb 01_hello.clj
 
Hello world

3. Jak rychlé je spouštění skriptů, informace o spotřebě dalších prostředků

Vyzkoušejme si nyní, jak rychle je spuštěn a dokončen skript „Hello world“:

$ time bb 01_hello.clj
 
Hello world
 
real    0m0.030s
user    0m0.010s
sys     0m0.017s

To není špatné; navíc se bb uložil do bufferu, takže druhé spuštění bude ještě rychlejší:

$ time bb 01_hello.clj
 
Hello world
 
real    0m0.014s
user    0m0.003s
sys     0m0.011s

Výsledek: spuštění interpretru je prakticky okamžité; žádné prodlevy, které známe z použití Leiningenu, zde nejsou patrné!

Ještě se podívejme, jaké vlastnosti má spuštěný proces z interpretrem. Vytvoříme nový skript, který po svém spuštění čeká na stisk klávesy Enter:

(println "Press Enter to continue...")
(read-line)

Skript spustíme v interpretru:

$ bb 02_wait_for_user.clj
Press Enter to continue...

Nalezneme PID procesu s interpretrem (je to první PID):

$ ps ax |grep 02_wait_for_user
 
14649 pts/1    Sl+    0:00 bb 02_wait_for_user.clj
14758 pts/2    S+     0:00 grep 02_wait_for_user

A podíváme se na podrobnější informace o tomto procesu, které jsou poskytované jádrem operačního systému:

$ cat /proc/14649/status
Name:   bb
Umask:  0002
State:  S (sleeping)
Tgid:   14649
Ngid:   0
Pid:    14649
PPid:   29490
TracerPid:      0
Uid:    1000    1000    1000    1000
Gid:    1000    1000    1000    1000
FDSize: 256
Groups: 39 982 1000 1001
NStgid: 14649
NSpid:  14649
NSpgid: 14649
NSsid:  29490
VmPeak:   227912 kB
VmSize:   163400 kB
VmLck:         0 kB
VmPin:         0 kB
VmHWM:     38576 kB
VmRSS:     38576 kB
RssAnon:            7012 kB
RssFile:           31564 kB
RssShmem:              0 kB
VmData:    23776 kB
VmStk:       132 kB
VmExe:     57612 kB
VmLib:         0 kB
VmPTE:       200 kB
VmSwap:        0 kB
HugetlbPages:          0 kB
CoreDumping:    0
Threads:        2
SigQ:   0/61760
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000000000
SigCgt: 2000000180001402
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
NoNewPrivs:     0
Seccomp:        0
Speculation_Store_Bypass:       thread vulnerable
Cpus_allowed:   ff
Cpus_allowed_list:      0-7
Mems_allowed:   00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
Mems_allowed_list:      0
voluntary_ctxt_switches:        9
nonvoluntary_ctxt_switches:     1
Poznámka: důležité jsou především informace začínající na „Vm“, které nám ukazují spotřebu operační paměti (RSS). Ta je – alespoň na dnešní poměry :-) – relativně malá a opět se jedná o mnohem menší hodnoty, než dvě či tři spuštěné JVM v případě použití Leiningenu.

4. Použití shebangu pro spuštění skriptů

Babashka je primárně určena pro tvorbu pomocných skriptů, které jsou příliš složité na to, aby je programátor či administrátor psal v BASHi. Takové skripty (nebo řekněme nástroje či filtry) se většinou nespouští zadáním názvu interpretru, ale přímo. I to je možné, protože můžeme na začátku skriptů použít známý shebang, který může obsahovat přímo cestu k interpretru:

#!/usr/local/bin/bb
 
(println "Hello" "world")

Nastavíme atribut x u skriptu:

$ chmod u+x 03_shebang.clj

A můžeme ho přímo spustit:

$ ./03_shebang.clj
 
Hello world
Poznámka: samozřejmě vůbec není nutné používat koncovku „.clj“

Čistější je spustit skript v modifikovaném prostředí, tudíž shebang příslušným způsobem změníme:

#!/usr/bin/env bb
 
(println "Hello" "world")

Použití je naprosto stejné, jako v předchozím příkladu:

$ chmod u+x 04_shebang.clj
 
$ ./04_shebang.clj
Hello world

5. Implicitní výstup ze skriptu

Ve skutečnosti lze „Hello world“ vypsat ještě kratším skriptem, než jaký byl uveden v předchozích kapitolách:

#!/usr/bin/env bb
 
"Hello world"

Tento skript pracuje tak, že poslední hodnotou (tedy řetězec) pošle na svůj standardní výstup.

Můžeme ovšem generovat i víceřádkový výstup, zde konkrétně v kombinaci se standardní funkcí println:

#!/usr/bin/env bb
 
(doseq [i (range 1 10)]
  (println i))
 
"Hello world"

S výsledkem:

1
2
3
4
5
6
7
8
9
"Hello world"

6. Hodnota navázaná na symbol *input*

Pokud se skript interpretovaný Babashkou spustí s přepínačem -i, bude standardní vstup (resp. přesněji řečeno jeho obsah) převeden na sekvenci a navázán na symbol *input* (symbol můžeme v tomto případě považovat za pojmenovanou konstantu, i když to není přesné). Obsah sekvence si tedy můžeme snadno vypsat:

(println *input*)

Příklad použití – na standardním vstupu bude seznam souborů, který bude převeden na sekvenci:

$ ls -1 | bb -i 07_print_input.clj
 
(01_hello.clj 02_wait_for_user.clj 03_shebang.clj 04_shebang.clj 05_implicit_output.clj 06_multiline_output.clj 07_print_input.clj 08_input_type.clj 09_sort_input.clj 10_for_each_input.clj 11_to_json.clj 12_cli_arguments.clj 13_cli_arguments.clj 14_http_get_to_text.clj 15_http_get_processing.clj 16_http_get_processing.clj 17_http_get_processing_args.clj 18_factorial_overflow.clj 19_factorial_bigint.clj 20_pi_computation_double.clj 21_pi_computation_rational.clj 22_sequential_map.clj 23_parallel_map.clj 24_parallel_map_clojure.clj license.clj README.md)

Snadno se můžeme přesvědčit, jakého typu vlastně vstup je:

(println (type *input*))

S výsledky:

$ ls -1 | bb -i 08_input_type.clj
 
clojure.lang.Cons

Se vstupem lze provádět různé operace, například ho setřídit a posléze vypsat (zde je použito threading makro):

(-> *input* sort println)

Otestování:

$ ls -1 | bb -i 09_sort_input.clj 
 
(01_hello.clj 02_wait_for_user.clj 03_shebang.clj 04_shebang.clj
05_implicit_output.clj 06_multiline_output.clj 07_print_input.clj
08_input_type.clj 09_sort_input.clj 10_for_each_input.clj 11_to_json.clj
12_cli_arguments.clj 13_cli_arguments.clj 14_http_get_to_text.clj
15_http_get_processing.clj 16_http_get_processing.clj 17_http_get_processing_args.clj
18_factorial_overflow.clj 19_factorial_bigint.clj 20_pi_computation_double.clj
21_pi_computation_rational.clj 22_sequential_map.clj 23_parallel_map.clj
24_parallel_map_clojure.clj README.md license.clj)

Taktéž můžeme sekvenci zpracovat prvek po prvku:

(doseq [i *input*]
  (println i))

Nyní bude výsledek odlišný:

$ ls -1 | bb -i 10_for_each_input.clj 
 
01_hello.clj
02_wait_for_user.clj
03_shebang.clj
04_shebang.clj
05_implicit_output.clj
06_multiline_output.clj
07_print_input.clj
08_input_type.clj
09_sort_input.clj
10_for_each_input.clj
11_to_json.clj
12_cli_arguments.clj
13_cli_arguments.clj
14_http_get_to_text.clj
15_http_get_processing.clj
16_http_get_processing.clj
17_http_get_processing_args.clj
18_factorial_overflow.clj
19_factorial_bigint.clj
20_pi_computation_double.clj
21_pi_computation_rational.clj
22_sequential_map.clj
23_parallel_map.clj
24_parallel_map_clojure.clj
license.clj
README.md

7. Dostupné balíčky

Babashka ovšem není „pouze“ interpretrem programovacího jazyka Clojure bez dalších podpůrných funkcí. Ve skutečnosti obsahuje Babashka relativně velké množství balíčků, které lze přímo použít (což do jisté míry vysvětluje i to, proč je spustitelný soubor bb tak velký). Tyto balíčky nebo jejich části by měly uspokojit velkou část programátorů při vytváření nástrojů spouštěných z příkazové řádky. Chybí prakticky jen plnohodnotný HTTP klient. Ostatně se můžete sami přesvědčit, které balíčky jsou dostupné, popř. které funkce a makra z nich je možné přímo použít, a to bez nutnosti importu externích balíčků nebo Javovských knihoven:

# Plné jméno Alias Podporované funkce
1 clojure.string str všechny
2 clojure.set set všechny
3 clojure.edn edn read-string
4 clojure.java.shell shell všechny
5 clojure.java.io io as-relative-path, as-url, copy, delete-file, file, input-stream, make-parents, output-stream, reader, resource, writer
6 clojure.main clojure.main repl
7 clojure.core.async async všechny
8 clojure.stacktrace × všechny
9 clojure.test × všechny
10 clojure.pprint × pprint
11 clojure.zip × všechny
12 clojure.tools.cli tools.cli všechny
13 clojure.data.csv csv všechny
14 clojure.data.xml xml všechny
15 cheshire.core json všechny
16 cognitect.transit transit všechny
17 clj-yaml.core yaml všechny
18 bencode.core bencode read-bencode, write-bencode
Poznámka: dostupný je i balíček s rozhraním JDBC, ten ovšem (pochopitelně) pro korektní práci vyžaduje i ovladače ke konkrétní databázi.

8. Výstup do JSONu

Některé balíčky a funkce či makra v nich definované si můžeme odzkoušet velmi snadno. V následujícím jednořádkovém demonstračním příkladu se hodnota na vstupu navázaná na symbol *input* převede do formátu JSON a vytiskne na standardní výstup (takže je JSON dostupný pro zpracování dalšími nástroji):

(println (json/encode *input*))

Vzhledem k tomu, že na vstupu je sekvence textových řádků, bude výstupní JSON obsahovat pole s řetězci, přičemž každý řetězec odpovídá jednomu vstupnímu řádku:

$ ls -1 | bb -i 11_to_json.clj
 
["01_hello.clj","02_wait_for_user.clj","03_shebang.clj","04_shebang.clj",
"05_implicit_output.clj","06_multiline_output.clj","07_print_input.clj",
"08_input_type.clj","09_sort_input.clj","10_for_each_input.clj","11_to_json.clj",
"12_cli_arguments.clj","13_cli_arguments.clj","14_http_get_to_text.clj",
"15_http_get_processing.clj","16_http_get_processing.clj","17_http_get_processing_args.clj",
"18_factorial_overflow.clj","19_factorial_bigint.clj","20_pi_computation_double.clj",
"21_pi_computation_rational.clj","22_sequential_map.clj","23_parallel_map.clj",
"24_parallel_map_clojure.clj","license.clj","README.md"]
Poznámka: pole v JSONu je totiž nejbližším ekvivalentem sekvencí, seznamů a vektorů, tedy datových struktur používaných v programovacím jazyku Clojure.

9. Zpracování argumentů předaných na příkazové řádce

Mnoho skriptů spouštěných z příkazové řádky (ať již jsou naprogramovány v jakémkoli jazyce) musí nějakým způsobem zpracovávat argumenty zadané uživatelem či jiným skriptem při volání. Pro tento účel se v programovacím jazyku Clojure používá balíček nazvaný clojure.tools.cli, který je dostupný i v nástroji Babashka. Bližší popis možností tohoto balíčku naleznete na stránce https://github.com/clojure/tools.cli. My si nyní v krátkosti ukážeme způsob použití pro skript, který má akceptovat argumenty -v, -h a argument s parametrem -p. Argumenty příkazové řádky se definují strukturou typu „vektor vektorů“ a zpracovávají funkcí parse-opts vracející informace o předaných parametrech:

(require '[clojure.pprint :as pprint])
(require '[clojure.tools.cli :refer [parse-opts]])
 
(def command-line-options
  [["-v" "--verbose" "Verbosity level"
    :id :verbosity
    :default 0
    :update-fn inc]
   ["-p" "--port PORT" "Port number"
    :default 80
    :parse-fn #(Integer/parseInt %)
    :validate [#(< 0 % 0x10000) "Must be a number between 0 and 65536"]]
   ["-h" "--help"]])
 
(pprint/pprint (parse-opts *command-line-args* command-line-options))
Poznámka: povšimněte si, že argumenty je možné i kontrolovat funkcí specifikovanou přes :validate (my jsme použili funkci anonymní, která zjišťuje, zda je číslo portu v rozsahu 1 až 65535).

Datová struktura vracená funkcí parse-opts je v čitelné podobě vypsána na standardní výstup, takže si můžeme otestovat, jak bude skript reagovat na různé argumenty, popř. na jejich absenci.

Spuštění bez argumentů:

$ bb 12_cli_arguments.clj
 
{:options {:verbosity 0, :port 80},
 :arguments [],
 :summary "  -v, --verbose        Verbosity level\n  -p, --port PORT  80  Port number\n  -h, --help",
 :errors nil}

Specifikace jediného argumentu:

$ bb 12_cli_arguments.clj -h
 
{:options {:verbosity 0, :port 80, :help true},
 :arguments [],
 :summary "  -v, --verbose        Verbosity level\n  -p, --port PORT  80  Port number\n  -h, --help",
 :errors nil}

Specifikace dvou argumentů, jeden má i hodnotu:

$ bb 12_cli_arguments.clj -h -p 42
 
{:options {:verbosity 0, :port 42, :help true},
 :arguments [],
 :summary "  -v, --verbose        Verbosity level\n  -p, --port PORT  80  Port number\n  -h, --help",
 :errors nil}

Použití delšího jména argumentu:

$ bb 12_cli_arguments.clj -h -p 42 --verbose
 
{:options {:verbosity 1, :port 42, :help true},
 :arguments [],
 :summary "  -v, --verbose        Verbosity level\n  -p, --port PORT  80  Port number\n  -h, --help",
 :errors nil}

Předání neznámých argumentů:

$ bb 12_cli_arguments.clj --foobar -x
 
{:options {:verbosity 0, :port 80},
 :arguments [],
 :summary "  -v, --verbose        Verbosity level\n  -p, --port PORT  80  Port number\n  -h, --help",
 :errors ["Unknown option: \"--foobar\"" "Unknown option: \"-x\""]}

Podobně lze tentýž přístup použít i ve chvíli, kdy je skript spustitelný a začíná shebangem:

#!/usr/bin/env bb
 
(require '[clojure.pprint :as pprint])
(require '[clojure.tools.cli :refer [parse-opts]])
 
(def command-line-options
  [["-v" "--verbose" "Verbosity level"
    :id :verbosity
    :default 0
    :update-fn inc]
   ["-p" "--port PORT" "Port number"
    :default 80
    :parse-fn #(Integer/parseInt %)
    :validate [#(< 0 % 0x10000) "Must be a number between 0 and 65536"]]
   ["-h" "--help"]])
 
(pprint/pprint (parse-opts *command-line-args* command-line-options))

Opět si skript několikrát spustíme.

Použití jediného argumentu v dlouhé podobě:

$ ./13_cli_arguments.clj --help
 
{:options {:verbosity 0, :port 80, :help true},
 :arguments [],
 :summary "  -v, --verbose        Verbosity level\n  -p, --port PORT  80  Port number\n  -h, --help",
 :errors nil}

Předání více argumentů v dlouhé podobě:

$ ./13_cli_arguments.clj --help --port 42 --verbose
 
{:options {:verbosity 1, :port 42, :help true},
 :arguments [],
 :summary "  -v, --verbose        Verbosity level\n  -p, --port PORT  80  Port number\n  -h, --help",
 :errors nil}

Předání více argumentů v krátké podobě:

$ ./13_cli_arguments.clj -h -p 42 -v
 
{:options {:verbosity 1, :port 42, :help true},
 :arguments [],
 :summary "  -v, --verbose        Verbosity level\n  -p, --port PORT  80  Port number\n  -h, --help",
 :errors nil}

Předání neznámých argumentů:

$ ./13_cli_arguments.clj -h -p 42 --foobar
 
{:options {:verbosity 0, :port 42, :help true},
 :arguments [],
 :summary "  -v, --verbose        Verbosity level\n  -p, --port PORT  80  Port number\n  -h, --help",
 :errors ["Unknown option: \"--foobar\""]}

10. Základní práce s HTTP/HTTPS, popř. s REST API

Velmi užitečná může být práce s HTTP či HTTPS, protože kombinace curl + sed nebo curl + jq nemusí být vždy to nejlepší možné řešení. Podívejme se na skript, který přistoupí na REST API a vypíše tělo odpovědi. Použijeme standardní funkci slurp:

(println (slurp "https://httpbin.org/get"))

Po spuštění tohoto skriptu by se na standardní výstup měl vypsat následující JSON:

{
  "args": {},
  "headers": {
    "Accept": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2",
    "Host": "httpbin.org",
    "User-Agent": "Java/11.0.7",
    "X-Amzn-Trace-Id": "Root=1-5f32df22-ecb57d4c0414da444b2dc6e8"
  },
  "origin": "37.48.9.246",
  "url": "https://httpbin.org/get"
}

11. Zpracování dat vracených ve formátu JSON

Předchozí skript lze samozřejmě nahradit voláním utility curl, ovšem předností Babashky (a vůbec Clojure) je excelentní práce se strukturovanými daty. Další skript opět přistoupí k REST API, ovšem z odpovědi nyní získá pouze některá data, konkrétně obsah hlavičky „User-Agent“. „Ukecaná“ verze skriptu by mohla vypadat následovně:

(let [response (slurp "https://httpbin.org/get")
      parsed   (json/decode response true)
      headers  (:headers parsed)
      user-agent (:User-Agent headers)]
  (println user-agent))

Po spuštění by se měl vypsat jediný řádek:

Java/11.0.7
Poznámka: zde Babashka prozrazuje, čím byla přeložena.

12. Ukázka síly jazyka Clojure – threading makro

Předchozí skript si můžeme přepsat do lepší podoby používající takzvané „threading makro“, které předává návratovou hodnotu nějaké formy jako první parametr další formy. Začíná se řetězcem, který se vyhodnotí sám na sebe. Tento řetězec je předán funkci slurp, která získá tělo odpovědi. To zpracujeme jako vstup v JSONu a následně můžeme přistupovat k jednotlivým atributům zkráceným voláním „get“:

(-> "https://httpbin.org/get"
    slurp
    (json/decode true)
    :headers
    :User-Agent
    println)

Výsledek bude shodný s předchozím skriptem:

Java/11.0.7
Poznámka: samozřejmě je možné celý skript napsat na jediný řádek, ovšem se zhoršenou čitelností.

Další skript je kombinací znalostí, které již máme. Skript akceptuje povinný argument s názvem serveru a z tohoto serveru se pak snaží získat odpověď přes jednoduché REST API. Pro větší zábavu je opět použito threading makro:

#!/usr/bin/env bb
 
(require '[clojure.pprint :as pprint])
(require '[clojure.tools.cli :refer [parse-opts]])
 
(import (java.net InetAddress))
 
(def command-line-options
  [["-H" "--hostname HOST" "Remote host"
    :default "localhost"
    :required true
    ]])
 
(let [opts (parse-opts *command-line-args* command-line-options)
      url  (-> opts :options :hostname)]
  (-> (str "https://" url "/get")
      slurp
      (json/decode true)
      pprint/pprint))

Příklad použití bez uvedení argumentu:

$ ./17_http_get_processing_args.clj
 
java.net.ConnectException: Connection refused (Connection refused) [at /home/ptisnovs/src/clojure/clojure-examples/babashka/17_http_get_processing_args.clj, line 18, column 7]

Příklad použití s uvedením argumentu:

$ ./17_http_get_processing_args.clj -H httpbin.org
 
{:args {},
 :headers {:Accept "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2",
           :Host "httpbin.org",
           :User-Agent "Java/11.0.7",
           :X-Amzn-Trace-Id "Root=1-5f32e7ed-23b74824f4391614a1e2853a"},
 :origin "37.48.9.246",
 :url "https://httpbin.org/get"}

13. Využití dalších vlastností Clojure – výpočty s neomezeným rozsahem hodnot

Podporovány jsou i další užitečné vlastnosti programovacího jazyka Clojure, například výpočty s neomezeným rozsahem hodnot (což je základní požadavek, který na vysokoúrovňové jazyky mám). Podívejme se na výpočet faktoriálu, který je založen na typu long, resp. přesněji řečeno java.lang.Long:

(defn factorial
    [n]
    (if (neg? n)
        (throw (IllegalArgumentException. "negative numbers are not supported!"))
        (apply * (range 1 (inc n)))))
 
(defn main
    [max]
    (doseq [i (range 0 (inc max))]
        (println i "! = " (factorial i))))
 
(main 50)

Tento výpočet pro větší hodnoty n skončí s chybou – a Clojure/Babashka správně tuto chybu detekuje:

$ bb 18_factorial_overflow.clj
 
0 ! =  1
1 ! =  1
2 ! =  2
3 ! =  6
4 ! =  24
5 ! =  120
6 ! =  720
7 ! =  5040
8 ! =  40320
9 ! =  362880
10 ! =  3628800
11 ! =  39916800
12 ! =  479001600
13 ! =  6227020800
14 ! =  87178291200
15 ! =  1307674368000
16 ! =  20922789888000
17 ! =  355687428096000
18 ! =  6402373705728000
19 ! =  121645100408832000
20 ! =  2432902008176640000
java.lang.ArithmeticException: integer overflow [at /home/ptisnovs/src/clojure/clojure-examples/babashka/18_factorial_overflow.clj, line 5, column 9]

Řešení tohoto problému je snadné – postačuje použít datový typ bigdec, tj. typ, který může obsahovat celočíselnou hodnotu o prakticky libovolném rozsahu (omezeni jsme dostupnou pamětí a výpočetním výkonem):

(defn factorial
    [n]
    (if (neg? n)
        (throw (IllegalArgumentException. "negative numbers are not supported!"))
        (apply * (range 1N (inc n)))))
 
(defn main
    [max]
    (doseq [i (range 0 (inc max))]
        (println i "! = " (factorial i))))
 
(main 50)

Nyní bude výpočet pokračovat pro libovolné hodnoty n:

$ bb 19_factorial_bigint.clj
 
0 ! =  1
1 ! =  1N
2 ! =  2N
3 ! =  6N
4 ! =  24N
5 ! =  120N
6 ! =  720N
7 ! =  5040N
8 ! =  40320N
9 ! =  362880N
10 ! =  3628800N
11 ! =  39916800N
12 ! =  479001600N
13 ! =  6227020800N
14 ! =  87178291200N
15 ! =  1307674368000N
16 ! =  20922789888000N
17 ! =  355687428096000N
18 ! =  6402373705728000N
19 ! =  121645100408832000N
20 ! =  2432902008176640000N
21 ! =  51090942171709440000N
22 ! =  1124000727777607680000N
23 ! =  25852016738884976640000N
24 ! =  620448401733239439360000N
25 ! =  15511210043330985984000000N
26 ! =  403291461126605635584000000N
27 ! =  10888869450418352160768000000N
28 ! =  304888344611713860501504000000N
29 ! =  8841761993739701954543616000000N
30 ! =  265252859812191058636308480000000N
31 ! =  8222838654177922817725562880000000N
32 ! =  263130836933693530167218012160000000N
33 ! =  8683317618811886495518194401280000000N
34 ! =  295232799039604140847618609643520000000N
35 ! =  10333147966386144929666651337523200000000N
36 ! =  371993326789901217467999448150835200000000N
37 ! =  13763753091226345046315979581580902400000000N
38 ! =  523022617466601111760007224100074291200000000N
39 ! =  20397882081197443358640281739902897356800000000N
40 ! =  815915283247897734345611269596115894272000000000N
41 ! =  33452526613163807108170062053440751665152000000000N
42 ! =  1405006117752879898543142606244511569936384000000000N
43 ! =  60415263063373835637355132068513997507264512000000000N
44 ! =  2658271574788448768043625811014615890319638528000000000N
45 ! =  119622220865480194561963161495657715064383733760000000000N
46 ! =  5502622159812088949850305428800254892961651752960000000000N
47 ! =  258623241511168180642964355153611979969197632389120000000000N
48 ! =  12413915592536072670862289047373375038521486354677760000000000N
49 ! =  608281864034267560872252163321295376887552831379210240000000000N
50 ! =  30414093201713378043612608166064768844377641568960512000000000000N

14. Datový typ double a počítání se zlomky

Jazyk Clojure podporuje i výpočty se zlomky (rational), což je opět ve světě LISPovských jazyků relativně často viděná vlastnost. Pokud totiž použijeme výchozí datový typ double, bude při výpočtech docházet ke všem chybám a problémům, které jsou způsobeny reprezentací hodnot dle IEEE 754 (proto se tento typ například nepoužívá v bankovnictví). Příkladem může být výpočet Pi iterativním vzorcem, u něhož nelze docílit vyšší přesnosti, než jaká odpovídá přesnosti a rozsahu typu double:

(defn compute-pi
  ([n pi]
  (loop [i 3
         pi pi]
    (if (<= i (+ n 2))
      (recur (+ i 2) (* pi (/ (- i 1) i) (/ (+ i 1) i)))
      pi)))
  ([n]
   (compute-pi n 4.0)))
 
 
(doseq [i (range 0 20)]
  (let [n (bit-shift-left 1 i)]
    (println n "\t" (compute-pi n))))

Po určitém počtu iterací se již přesnost nezvyšuje:

1        3.5555555555555554
2        3.5555555555555554
4        3.4133333333333336
8        3.3023935500125976
16       3.2300364664117205
32       3.1881271694471423
64       3.1654820600348006
128      3.1536988490958002
256      3.1476868995564105
512      3.144650162517202
1024     3.1431240170281978
2048     3.1423589891217905
4096     3.141975985005618
8192     3.1417843602347597
16384    3.1416885171496745
32768    3.141640587929478
65536    3.1416166213995473
131072   3.1416046376545177
262144   3.141598645661904
524288   3.1415956496356134

Výpočet ovšem můžeme provést se zlomky a to tak, že ve zdrojovém kódu nikde nepoužijeme typ double. Potom například zápis (/ x y) znamená, že výsledkem výpočtu bude zlomek (zkrácený do minimální podoby). Povšimněte si, že se tím řeší všechny obvyklé problémy, které dělení v programovacích jazycích způsobuje – typickým příkladem je Python s velmi problematickým přístupem.

(defn compute-pi
  ([n pi]
  (loop [i 3
         pi pi]
    (if (<= i (+ n 2))
      (recur (+ i 2) (* pi (/ (- i 1) i) (/ (+ i 1) i)))
      pi)))
  ([n]
   (compute-pi n 4)))
 
 
(doseq [i (range 0 6)]
  (let [n (bit-shift-left 1 i)]
    (println n "\t" (compute-pi n))))

Výsledky nyní budou vždy ve tvaru (zkráceného) zlomku:

1        32/9
2        32/9
4        256/75
8        65536/19845
16       4294967296/1329696225
32       18446744073709551616/5786075364399106425
...
...
...

15. Výpočty ve více vláknech – map versus pmap

Velkou předností Babashky oproti jiným jednodušším technologiím je možnost spuštění částí algoritmu ve více vláknech. V jazyce Clojure k tomuto účelu existuje několik funkcí a maker. Pravděpodobně nejjednodušeji je použitelná funkce nazvaná pmap, která je obdobou funkce vyššího řádu map, ovšem s tím, že zpracování vstupu (aplikace funkce) je prováděno ve větším množství vláken přidělených z thread poolu (nemusí se tedy vždy vytvářet znovu). Nejprve si ukažme, jakým způsobem by bylo možné vypočítat konstantu Pi algoritmem popsaným zde pro počet iterací mezi hodnotou 1000000 a 1000020. Jedná se o vysoké počty iterací, takže sekvenční výpočet pro vstup 1000000, 1000001 atd. nebude příliš rychlý:

(defn compute-pi
  ([n pi]
  (loop [i 3
         pi pi]
    (if (<= i (+ n 2))
      (recur (+ i 2) (* pi (/ (- i 1) i) (/ (+ i 1) i)))
      pi)))
  ([n]
   (compute-pi n 4.0)))
 
(let [n (range 1000000 1000020)
      results (doall (map #(compute-pi %) n))]
  (doseq [pi results]
    (println pi)))

Celý výpočet lze ovšem provést i paralelně a to velmi jednoduše – náhradou funkce map za funkci pmap neboli „parallel map“:

(defn compute-pi
  ([n pi]
  (loop [i 3
         pi pi]
    (if (<= i (+ n 2))
      (recur (+ i 2) (* pi (/ (- i 1) i) (/ (+ i 1) i)))
      pi)))
  ([n]
   (compute-pi n 4.0)))
 
(let [n (range 1000000 1000020)
      results (doall (pmap #(compute-pi %) n))]
  (doseq [pi results]
    (println pi)))

Nyní si oba časy výpočtu můžeme porovnat. Nejprve sekvenční výpočet založený na funkci map (které se předala funkce anonymní – znak # lze číst jako λ):

$ time bb 22_sequential_map.clj
 
3.141594224383251
3.14159422438011
3.14159422438011
3.141594224376968
3.141594224376968
3.1415942243738266
3.1415942243738266
3.1415942243706847
3.1415942243706847
3.141594224367543
3.141594224367543
3.1415942243644017
3.1415942243644017
3.1415942243612593
3.1415942243612593
3.141594224358117
3.141594224358117
3.141594224354974
3.141594224354974
3.1415942243518313
 
real    1m15.040s
user    1m14.825s
sys     0m0.088s

Ve druhém případě je použit výpočet paralelní:

$ time bb 23_parallel_map.clj
 
3.141594224383251
3.14159422438011
3.14159422438011
3.141594224376968
3.141594224376968
3.1415942243738266
3.1415942243738266
3.1415942243706847
3.1415942243706847
3.141594224367543
3.141594224367543
3.1415942243644017
3.1415942243644017
3.1415942243612593
3.1415942243612593
3.141594224358117
3.141594224358117
3.141594224354974
3.141594224354974
3.1415942243518313
 
real    0m41.497s
user    1m40.592s
sys     0m3.002s
Poznámka: výsledky byly vypočteny přibližně dvakrát rychleji, což znamená, že celý výpočet z nějakého důvodu neškáluje tak dobře, jak by se mohlo na stroji se čtyřmi jádry očekávat. Nicméně například pro souběžné stahování souborů, přístup k REST API apod. se může jednat o vhodný přístup.

16. Porovnání rychlosti výpočtů Babashky s implementací Clojure pro JVM

V úvodní kapitole jsme si řekli, že se Babashka hodí především pro spouštění takových skriptů, v nichž se neprovádí složité výpočty. Je tomu tak z toho důvodu, že interpret (SCI) nemůže u dlouhodobějších výpočtů konkurovat původnímu Clojure, které provádí překlad do bajtkódu s jeho následným JITováním. Ostatně si to můžeme snadno otestovat – spustíme výpočet čísla Pi s běžným Clojure (starší verze 1.8.0 byla zvolena proto, že je spustitelná z příkazové řádky velmi snadno, bez nutnosti složitější manipulace s CLASSPATH):

$ time java -cp clojure-1.8.0.jar clojure.main 22_sequential_map.clj
 
3.141594224383251
3.14159422438011
3.14159422438011
3.141594224376968
3.141594224376968
3.1415942243738266
3.1415942243738266
3.1415942243706847
3.1415942243706847
3.141594224367543
3.141594224367543
3.1415942243644017
3.1415942243644017
3.1415942243612593
3.1415942243612593
3.141594224358117
3.141594224358117
3.141594224354974
3.141594224354974
3.1415942243518313
 
real    0m7.150s
user    0m8.078s
sys     0m0.280s

Výpočet (sekvenční) je znatelně rychlejší (osm sekund oproti minutě a patnácti sekundám), takže se ještě podívejme na možnosti paralelizace výpočtu s využitím funkce pmap:

$ time java -cp clojure-1.8.0.jar clojure.main 24_parallel_map_clojure.clj
 
3.141594224383251
3.14159422438011
3.14159422438011
3.141594224376968
3.141594224376968
3.1415942243738266
3.1415942243738266
3.1415942243706847
3.1415942243706847
3.141594224367543
3.141594224367543
3.1415942243644017
3.1415942243644017
3.1415942243612593
3.1415942243612593
3.141594224358117
3.141594224358117
3.141594224354974
3.141594224354974
3.1415942243518313
 
real    0m2.590s
user    0m14.539s
sys     0m0.585s

Nyní byl výpočet dokončen za necelé tři sekundy!

Poznámka: demonstrační příklad bylo nutné nepatrně upravit, aby se po ukončení výpočtů ihned ukončila i celá JVM a nečekalo se na vlákna vracená do thread poolu:
(defn compute-pi
  ([n pi]
  (loop [i 3
         pi pi]
    (if (<= i (+ n 2))
      (recur (+ i 2) (* pi (/ (- i 1) i) (/ (+ i 1) i)))
      pi)))
  ([n]
   (compute-pi n 4.0)))
 
(let [n (range 1000000 1000020)
      results (doall (pmap #(compute-pi %) n))]
  (doseq [pi results]
    (println pi)))
 
(System/exit 0)

17. Zobrazení nápovědy přímo v interaktivní smyčce REPL

Poslední užitečnou vlastností nástroje Babashka, s níž se dnes seznámíme, je možnost zobrazení nápovědy k implementovaným funkcím přímo z interaktivní smyčky REPL. To mj. znamená, že není zapotřebí dodávat další soubory s dokumentací, info stránky atd. Postačuje pouze spustit REPL:

$ bb

a následně použít makro doc pro zobrazení příslušné nápovědy:

Linuxová školení

user=> (doc first)
-------------------------
clojure.core/first
([coll])
  Returns the first item in the collection. Calls seq on its
    argument. If coll is nil, returns nil.
nil
 
user=> (doc rest)
-------------------------
clojure.core/rest
([coll])
  Returns a possibly empty seq of the items after the first. Calls seq on its
  argument.
nil
 
user=> (doc str)
-------------------------
clojure.core/str
([] [x] [x & ys])
  With no args, returns the empty string. With one arg x, returns
  x.toString().  (str nil) returns the empty string. With more than
  one arg, returns the concatenation of the str values of the args.
nil
 

Nápovědu lze získat i k funkcím, které se nachází ve standardně dostupných balíčcích zmíněných v sedmé kapitole:

user=> (doc json/decode)
-------------------------
cheshire.core/decode
([string] [string key-fn] [string key-fn array-coerce-fn])
  Alias to parse-string for clojure-json users
Poznámka: současná verze Babashky nedokáže zobrazit nápovědu k makrům, což pravděpodobně souvisí s neexistencí podpory pro funkci eval.

18. Repositář s demonstračními příklady

Všechny dnes ukázané demonstrační příklady určené pro poslední verzi projektu Babashka byly uloženy do repositáře, který naleznete na adrese https://github.com/tisnik/clojure-examples, konkrétně do adresáře https://github.com/tisnik/clojure-examples/tree/master/babashka:

# Projekt Popis Odkaz
1 01_hello.clj klasický příklad typu „Hello world!“ https://github.com/tisnik/clojure-examples/tree/master/babashka/01_he­llo.clj
2 02_wait_for_user.clj čekání na vstup od uživatele (pro získání charakteristik procesu) https://github.com/tisnik/clojure-examples/tree/master/babashka/02_wa­it_for_user.clj
3 03_shebang.clj použití shebangu pro spuštění skriptů https://github.com/tisnik/clojure-examples/tree/master/babashka/03_she­bang.clj
4 04_shebang.clj použití shebangu pro spuštění skriptů https://github.com/tisnik/clojure-examples/tree/master/babashka/04_she­bang.clj
5 05_implicit_output.clj implicitní výstup ze skriptu https://github.com/tisnik/clojure-examples/tree/master/babashka/05_im­plicit_output.clj
6 06_multiline_output.clj implicitní a víceřádkový výstup ze skriptu https://github.com/tisnik/clojure-examples/tree/master/babashka/06_mul­tiline_output.clj
7 07_print_input.clj tisk hodnoty navázané na symbol *input* https://github.com/tisnik/clojure-examples/tree/master/babashka/07_prin­t_input.clj
8 08_input_type.clj získání typu hodnoty navázané na symbol *input* https://github.com/tisnik/clojure-examples/tree/master/babashka/08_in­put_type.clj
9 09_sort_input.clj setřídění hodnot na vstupu https://github.com/tisnik/clojure-examples/tree/master/babashka/09_sor­t_input.clj
10 10_for_each_input.clj programová smyčka https://github.com/tisnik/clojure-examples/tree/master/babashka/10_for_e­ach_input.clj
11 11_to_json.clj výstup do formátu JSON https://github.com/tisnik/clojure-examples/tree/master/babashka/11_to_json­.clj
12 12_cli_arguments.clj zpracování argumentů na příkazové řádce https://github.com/tisnik/clojure-examples/tree/master/babashka/12_cli_ar­guments.clj
13 13_cli_arguments.clj zpracování argumentů na příkazové řádce https://github.com/tisnik/clojure-examples/tree/master/babashka/13_cli_ar­guments.clj
14 14_http_get_to_text.clj jednoduchý HTTP dotaz typu GET https://github.com/tisnik/clojure-examples/tree/master/babashka/14_http_get_to_tex­t.clj
15 15_http_get_processing.clj vylepšení předchozího příkladu https://github.com/tisnik/clojure-examples/tree/master/babashka/15_http_get_pro­cessing.clj
16 16_http_get_processing.clj zpracování výsledků získaných přes REST API https://github.com/tisnik/clojure-examples/tree/master/babashka/16_http_get_pro­cessing.clj
17 17_http_get_processing_args.clj zadání URL z příkazového řádku https://github.com/tisnik/clojure-examples/tree/master/babashka/17_http_get_pro­cessing_args.clj
18 18_factorial_overflow.clj výpočet faktoriálu s přetečením výsledků https://github.com/tisnik/clojure-examples/tree/master/babashka/18_fac­torial_overflow.clj
19 19_factorial_bigint.clj výpočet faktoriálu s prakticky neomezeným rozsahem https://github.com/tisnik/clojure-examples/tree/master/babashka/19_fac­torial_bigint.clj
20 20_pi_computation_double.clj výpočet konstanty Pi s datovým typem double https://github.com/tisnik/clojure-examples/tree/master/babashka/20_pi_com­putation_double.clj
21 21_pi_computation_rational.clj výpočet konstanty Pi s datovým typem zlomek https://github.com/tisnik/clojure-examples/tree/master/babashka/21_pi_com­putation_rational.clj
22 22_sequential_map.clj sekvenční výpočet Pi s využitím funkce map https://github.com/tisnik/clojure-examples/tree/master/babashka/22_se­quential_map.clj
23 23_parallel_map.clj paralelní výpočet Pi s využitím funkce pmap https://github.com/tisnik/clojure-examples/tree/master/babashka/23_pa­rallel_map.clj
24 24_parallel_map_clojure.clj úprava předchozího příkladu pro klasické Clojure https://github.com/tisnik/clojure-examples/tree/master/babashka/24_pa­rallel_map_clojure.clj

19. Odkazy na předchozí části seriálu o programovacím jazyku Clojure

  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. Programovací jazyk Clojure – triky při práci s řetězci
    http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-retezci/
  17. Programovací jazyk Clojure – triky při práci s kolekcemi
    http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-kolekcemi/
  18. Programovací jazyk Clojure – práce s mapami a množinami
    http://www.root.cz/clanky/programovaci-jazyk-clojure-prace-s-mapami-a-mnozinami/
  19. Programovací jazyk Clojure – základy zpracování XML
    http://www.root.cz/clanky/programovaci-jazyk-clojure-zaklady-zpracovani-xml/
  20. Programovací jazyk Clojure – testování s využitím knihovny Expectations
    http://www.root.cz/clanky/programovaci-jazyk-clojure-testovani-s-vyuzitim-knihovny-expectations/
  21. 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/
  22. Enlive – výkonný šablonovací systém pro jazyk Clojure
    http://www.root.cz/clanky/enlive-vykonny-sablonovaci-system-pro-jazyk-clojure/
  23. 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/
  24. Novinky v Clojure verze 1.8.0
    http://www.root.cz/clanky/novinky-v-clojure-verze-1–8–0/
  25. 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/
  26. 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/
  27. 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/
  28. Vytváříme IRC bota v programovacím jazyce Clojure
    http://www.root.cz/clanky/vytvarime-irc-bota-v-programovacim-jazyce-clojure/
  29. Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
    https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/
  30. Multimetody v Clojure aneb polymorfismus bez použití OOP
    https://www.root.cz/clanky/multimetody-v-clojure-aneb-polymorfismus-bez-pouziti-oop/
  31. Práce s externími Java archivy v programovacím jazyku Clojure
    https://www.root.cz/clanky/prace-s-externimi-java-archivy-v-programovacim-jazyku-clojure/
  32. Clojure 16: Složitější uživatelská makra
    http://www.root.cz/clanky/programovaci-jazyk-clojure-16-slozitejsi-uzivatelska-makra/
  33. Clojure 17: Využití standardních maker v praxi
    http://www.root.cz/clanky/programovaci-jazyk-clojure-17-vyuziti-standardnich-maker-v-praxi/
  34. Clojure 18: Základní techniky optimalizace aplikací
    http://www.root.cz/clanky/programovaci-jazyk-clojure-18-zakladni-techniky-optimalizace-aplikaci/
  35. Clojure 19: Vývojová prostředí pro Clojure
    http://www.root.cz/clanky/programovaci-jazyk-clojure-19-vyvojova-prostredi-pro-clojure/
  36. 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/
  37. Clojure 21: ClojureScript aneb překlad Clojure do JS
    http://www.root.cz/clanky/programovaci-jazyk-clojure-21-clojurescript-aneb-preklad-clojure-do-javascriptu/
  38. Leiningen: nástroj pro správu projektů napsaných v Clojure
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure/
  39. 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/
  40. 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/
  41. 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/
  42. 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/
  43. 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/
  44. Programovací jazyk Clojure a databáze (1.část)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-databaze-1-cast/
  45. Pluginy pro Leiningen
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-pluginy-pro-leiningen/
  46. 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/
  47. 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/
  48. 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/
  49. 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/
  50. Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure
    http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure/
  51. 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/
  52. 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/
  53. Programovací jazyk Clojure a práce s Gitem
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem/
  54. Programovací jazyk Clojure a práce s Gitem (2)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem-2/
  55. 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/
  56. Pixie: lehký skriptovací jazyk s „kouzelnými“ schopnostmi
    https://www.root.cz/clanky/pixie-lehky-skriptovaci-jazyk-s-kouzelnymi-schopnostmi/
  57. Programovací jazyk Pixie: funkce ze základní knihovny a použití FFI
    https://www.root.cz/clanky/pro­gramovaci-jazyk-pixie-funkce-ze-zakladni-knihovny-a-pouziti-ffi/
  58. Novinky v Clojure verze 1.9.0
    https://www.root.cz/clanky/novinky-v-clojure-verze-1–9–0/
  59. Validace dat s využitím knihovny spec v Clojure 1.9.0
    https://www.root.cz/clanky/validace-dat-s-vyuzitim-knihovny-spec-v-clojure-1–9–0/
  60. Použití jazyka Gherkin při tvorbě testovacích scénářů pro aplikace psané v Clojure
    https://www.root.cz/clanky/pouziti-jazyka-gherkin-pri-tvorbe-testovacich-scenaru-pro-aplikace-psane-v-nbsp-clojure/
  61. Použití jazyka Gherkin při tvorbě testovacích scénářů pro aplikace psané v Clojure (2)
    https://www.root.cz/clanky/pouziti-jazyka-gherkin-pri-tvorbe-testovacich-scenaru-pro-aplikace-psane-v-nbsp-clojure-2/
  62. Incanter: prostředí pro statistické výpočty s grafickým výstupem založené na Clojure
    https://www.root.cz/clanky/incanter-prostredi-pro-statisticke-vypocty-s-grafickym-vystupem-zalozene-na-clojure/
  63. Incanter: operace s maticemi
    https://www.root.cz/clanky/incanter-operace-s-maticemi/
  64. Pixie: lehký skriptovací jazyk s „kouzelnými“ schopnostmi
    https://www.root.cz/clanky/pixie-lehky-skriptovaci-jazyk-s-kouzelnymi-schopnostmi/
  65. Programovací jazyk Pixie: funkce ze základní knihovny a použití FFI
    https://www.root.cz/clanky/pro­gramovaci-jazyk-pixie-funkce-ze-zakladni-knihovny-a-pouziti-ffi/
  66. Novinky v Clojure verze 1.9.0
    https://www.root.cz/clanky/novinky-v-clojure-verze-1–9–0/
  67. Validace dat s využitím knihovny spec v Clojure 1.9.0
    https://www.root.cz/clanky/validace-dat-s-vyuzitim-knihovny-spec-v-clojure-1–9–0/
  68. Použití jazyka Gherkin při tvorbě testovacích scénářů pro aplikace psané v Clojure
    https://www.root.cz/clanky/pouziti-jazyka-gherkin-pri-tvorbe-testovacich-scenaru-pro-aplikace-psane-v-nbsp-clojure/
  69. Použití jazyka Gherkin při tvorbě testovacích scénářů pro aplikace psané v Clojure (2)
    https://www.root.cz/clanky/pouziti-jazyka-gherkin-pri-tvorbe-testovacich-scenaru-pro-aplikace-psane-v-nbsp-clojure-2/
  70. Interpret programovacího jazyka Clojure integrovaný do Jupyter Notebooku
    https://www.root.cz/clanky/interpret-programovaciho-jazyka-clojure-integrovany-do-jupyter-notebooku/

20. Odkazy na Internetu

  1. babashka: A Clojure babushka for the grey areas of Bash
    https://github.com/borkdude/babashka
  2. Babashka and the Small Clojure Interpreter @ ClojureD 2020 (slajdy)
    https://speakerdeck.com/bor­kdude/babashka-and-the-small-clojure-interpreter-at-clojured-2020
  3. Babashka: ukázky použití
    https://github.com/borkdu­de/babashka/blob/master/doc/e­xamples.md
  4. clojureD 2020: „Babashka and Small Clojure Interpreter: Clojure in new contexts“ by Michiel Borkent
    https://www.youtube.com/watch?v=Nw8aN-nrdEk&t=5s
  5. Meetup #124 Babashka, implementing an nREPL server & game engines with Clojure
    https://www.youtube.com/wat­ch?v=0YmZYnwyHHc
  6. The Last Programming Language (shrnutí vývoje programovacích jazyků)
    https://www.youtube.com/watch?v=P2yr-3F6PQo
  7. Shebang (Unix): Wikipedia EN
    https://en.wikipedia.org/wi­ki/Shebang_(Unix)
  8. Shebang (Unix): Wikipedia CZ
    https://cs.wikipedia.org/wi­ki/Shebang_(Unix)
  9. How to create Clojure notebooks in Jupyter
    https://s01blog.wordpress­.com/2017/12/10/how-to-create-clojure-notebooks-in-jupyter/
  10. Dokumentace k nástroji Conda
    https://docs.conda.io/en/latest/
  11. Notebook interface
    https://en.wikipedia.org/wi­ki/Notebook_interface
  12. Jypyter: open source, interactive data science and scientific computing across over 40 programming languages
    https://jupyter.org/
  13. Calysto Scheme
    https://github.com/Calysto/ca­lysto_scheme
  14. scheme.py (základ projektu Calysto Scheme)
    https://github.com/Calysto/ca­lysto_scheme/blob/master/ca­lysto_scheme/scheme.py
  15. Humane test output for clojure.test
    https://github.com/pjstadig/humane-test-output
  16. iota
    https://github.com/juxt/iota
  17. 5 Differences between clojure.spec and Schema
    https://lispcast.com/clojure.spec-vs-schema/
  18. Schema: Clojure(Script) library for declarative data description and validation
    https://github.com/plumatic/schema
  19. Zip archiv s Clojure 1.9.0
    http://repo1.maven.org/ma­ven2/org/clojure/clojure/1­.9.0/clojure-1.9.0.zip
  20. Clojure 1.9 is now available
    https://clojure.org/news/2017/12/08/clo­jure19
  21. Deps and CLI Guide
    https://clojure.org/guides/dep­s_and_cli
  22. Changes to Clojure in Version 1.9
    https://github.com/clojure/clo­jure/blob/master/changes.md
  23. clojure.spec – Rationale and Overview
    https://clojure.org/about/spec
  24. Zip archiv s Clojure 1.8.0
    http://repo1.maven.org/ma­ven2/org/clojure/clojure/1­.8.0/clojure-1.8.0.zip
  25. Clojure 1.8 is now available
    http://clojure.org/news/2016/01/19/clo­jure18
  26. Socket Server REPL
    http://dev.clojure.org/dis­play/design/Socket+Server+REPL
  27. CLJ-1671: Clojure socket server
    http://dev.clojure.org/jira/browse/CLJ-1671
  28. CLJ-1449: Add clojure.string functions for portability to ClojureScript
    http://dev.clojure.org/jira/browse/CLJ-1449
  29. Launching a Socket Server
    http://clojure.org/referen­ce/repl_and_main#_launchin­g_a_socket_server
  30. API for clojure.string
    http://clojure.github.io/clo­jure/branch-master/clojure.string-api.html
  31. Clojars:
    https://clojars.org/
  32. Seznam knihoven na Clojars:
    https://clojars.org/projects
  33. Clojure Cookbook: Templating HTML with Enlive
    https://github.com/clojure-cookbook/clojure-cookbook/blob/master/07_webapps/7–11_enlive.asciidoc
  34. An Introduction to Enlive
    https://github.com/swannodette/enlive-tutorial/
  35. Enlive na GitHubu
    https://github.com/cgrand/enlive
  36. Expectations: příklady atd.
    http://jayfields.com/expectations/
  37. Expectations na GitHubu
    https://github.com/jaycfi­elds/expectations
  38. Lein-expectations na GitHubu
    https://github.com/gar3thjon3s/lein-expectations
  39. Testing Clojure With Expectations
    https://semaphoreci.com/blog/2014/09/23/tes­ting-clojure-with-expectations.html
  40. 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/
  41. Testing: One assertion per test
    http://blog.jayfields.com/2007/06/tes­ting-one-assertion-per-test.html
  42. Rewriting Your Test Suite in Clojure in 24 hours
    http://blog.circleci.com/rewriting-your-test-suite-in-clojure-in-24-hours/
  43. Clojure doc: zipper
    http://clojuredocs.org/clo­jure.zip/zipper
  44. Clojure doc: parse
    http://clojuredocs.org/clo­jure.xml/parse
  45. Clojure doc: xml-zip
    http://clojuredocs.org/clojure.zip/xml-zip
  46. Clojure doc: xml-seq
    http://clojuredocs.org/clo­jure.core/xml-seq
  47. Parsing XML in Clojure
    https://github.com/clojuredocs/guides
  48. Clojure Zipper Over Nested Vector
    https://vitalyper.wordpres­s.com/2010/11/23/clojure-zipper-over-nested-vector/
  49. Understanding Clojure's PersistentVector implementation
    http://blog.higher-order.net/2009/02/01/understanding-clojures-persistentvector-implementation
  50. Understanding Clojure's PersistentHashMap (deftwice…)
    http://blog.higher-order.net/2009/09/08/understanding-clojures-persistenthashmap-deftwice.html
  51. Assoc and Clojure's PersistentHashMap: part ii
    http://blog.higher-order.net/2010/08/16/assoc-and-clojures-persistenthashmap-part-ii.html
  52. Ideal Hashtrees (paper)
    http://lampwww.epfl.ch/pa­pers/idealhashtrees.pdf
  53. Clojure home page
    http://clojure.org/
  54. Clojure (downloads)
    http://clojure.org/downloads
  55. Clojure Sequences
    http://clojure.org/sequences
  56. Clojure Data Structures
    http://clojure.org/data_structures
  57. 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
  58. 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
  59. Clojure – Functional Programming for the JVM
    http://java.ociweb.com/mar­k/clojure/article.html
  60. Clojure quick reference
    http://faustus.webatu.com/clj-quick-ref.html
  61. 4Clojure
    http://www.4clojure.com/
  62. ClojureDoc (rozcestník s dokumentací jazyka Clojure)
    http://clojuredocs.org/
  63. Clojure (na Wikipedia EN)
    http://en.wikipedia.org/wiki/Clojure
  64. Clojure (na Wikipedia CS)
    http://cs.wikipedia.org/wiki/Clojure
  65. SICP (The Structure and Interpretation of Computer Programs)
    http://mitpress.mit.edu/sicp/
  66. Pure function
    http://en.wikipedia.org/wi­ki/Pure_function
  67. Funkcionální programování
    http://cs.wikipedia.org/wi­ki/Funkcionální_programová­ní
  68. Čistě funkcionální (datové struktury, jazyky, programování)
    http://cs.wikipedia.org/wi­ki/Čistě_funkcionální
  69. 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
  70. Clojure Macro Tutorial (Part II: The Compiler Strikes Back)
    http://www.learningclojure­.com/2010/09/clojure-macro-tutorial-part-ii-compiler.html
  71. Clojure Macro Tutorial (Part III: Syntax Quote)
    http://www.learningclojure­.com/2010/09/clojure-macro-tutorial-part-ii-syntax.html
  72. Tech behind Tech: Clojure Macros Simplified
    http://techbehindtech.com/2010/09/28/clo­jure-macros-simplified/
  73. Fatvat – Exploring functional programming: Clojure Macros
    http://www.fatvat.co.uk/2009/02/clo­jure-macros.html
  74. Eulerovo číslo
    http://cs.wikipedia.org/wi­ki/Eulerovo_číslo
  75. List comprehension
    http://en.wikipedia.org/wi­ki/List_comprehension
  76. List Comprehensions in Clojure
    http://asymmetrical-view.com/2008/11/18/list-comprehensions-in-clojure.html
  77. Clojure Programming Concepts: List Comprehension
    http://en.wikibooks.org/wi­ki/Clojure_Programming/Con­cepts#List_Comprehension
  78. Clojure core API: for macro
    http://clojure.github.com/clo­jure/clojure.core-api.html#clojure.core/for
  79. cirrus machina – The Clojure for macro
    http://www.cirrusmachina.com/blog/com­ment/the-clojure-for-macro/
  80. Riastradh's Lisp Style Rules
    http://mumble.net/~campbe­ll/scheme/style.txt
  81. Dynamic Languages Strike Back
    http://steve-yegge.blogspot.cz/2008/05/dynamic-languages-strike-back.html
  82. Scripting: Higher Level Programming for the 21st Century
    http://www.tcl.tk/doc/scripting.html
  83. Java Virtual Machine Support for Non-Java Languages
    http://docs.oracle.com/ja­vase/7/docs/technotes/gui­des/vm/multiple-language-support.html
  84. Třída java.lang.String
    http://docs.oracle.com/ja­vase/7/docs/api/java/lang/Strin­g.html
  85. Třída java.lang.StringBuffer
    http://docs.oracle.com/ja­vase/7/docs/api/java/lang/Strin­gBuffer.html
  86. Třída java.lang.StringBuilder
    http://docs.oracle.com/ja­vase/7/docs/api/java/lang/Strin­gBuilder.html
  87. StringBuffer versus String
    http://www.javaworld.com/ar­ticle/2076072/build-ci-sdlc/stringbuffer-versus-string.html
  88. Threading macro (dokumentace k jazyku Clojure)
    https://clojure.github.io/clo­jure/clojure.core-api.html#clojure.core/->
  89. Understanding the Clojure → macro
    http://blog.fogus.me/2009/09/04/un­derstanding-the-clojure-macro/
  90. clojure.inspector
    http://clojure.github.io/clo­jure/clojure.inspector-api.html
  91. The Clojure Toolbox
    http://www.clojure-toolbox.com/
  92. Unit Testing in Clojure
    http://nakkaya.com/2009/11/18/unit-testing-in-clojure/
  93. Testing in Clojure (Part-1: Unit testing)
    http://blog.knoldus.com/2014/03/22/tes­ting-in-clojure-part-1-unit-testing/
  94. API for clojure.test – Clojure v1.6 (stable)
    https://clojure.github.io/clo­jure/clojure.test-api.html
  95. Leiningen: úvodní stránka
    http://leiningen.org/
  96. Leiningen: Git repository
    https://github.com/techno­mancy/leiningen
  97. leiningen-win-installer
    http://leiningen-win-installer.djpowell.net/
  98. Clojure.org: Vars and the Global Environment
    http://clojure.org/Vars
  99. Clojure.org: Refs and Transactions
    http://clojure.org/Refs
  100. Clojure.org: Atoms
    http://clojure.org/Atoms
  101. Clojure.org: Agents as Asynchronous Actions
    http://clojure.org/agents
  102. Transient Data Structures
    http://clojure.org/transients