Created by Pavel Klavík
ClojureScript je varianta jazyka, která se kompiluje do JavaScriptu. K tomu využíva Google Closure Compiler, který optimalizuje a produkuje minimální kód.
Původní implementace Clojure adresovala jak JVM, tak CLR (.NET). Dnes se převážně používá JVM verze, ale CLR verze by pořád měla být funkční.
Originální nejvíce rozšířená verze Clojure běží nad JVM. Lze ji dobře integrovat s existujícími Javovskými knihovnami a naopak vytvářet nové knihovny použitelné přímo z Javy. Stačí přidat clojure.jar do projektu.
Existují i verze Clojure, které adresují další platformy.
Jazyk je hostovaný na ostatních platformách a máme přímo přístup k prostředí. Lze tedy přímočaře využít libovolnou knihovnu z prostředí a naopak nativní kód z prostředí může používat kód napsaný v Clojure. Proto je snadné začlenit Clojure do již existujícího projektu. V řadě firem byl Clojure nastrčen nějakým programátorem a postupně ho adaptovali celé týmy.
function performMagic(value, list) {
return list.concat(value)
.map(v => v + 1)
.filter(v => v % 2 === 0)
.reduce((accumulator, v) => v + accumulator);
}(defn perform-magic [value list]
(->> list
(cons value)
(map inc)
(filter odd?)
(reduce +)))
Místo f(x,y,z) se píše (f x y z).
Clojure má dynamické typy podle prostředí. Okolo statického typování panuje celá řada mýtů:
Statické typy ale převážně adresují překlepy (třeba prohození pořadí argumentů). Seriózní problémy neodhalí. Cena za to je menší obecnost a spousta zbytečného kódu.
V Clojure lze použít automatické testy, REPL a spec.
Použivají ho firmy jako Apple, Facebook, Netflix, WalmartLabs, ...
Ilustrace na úpravách v kódu hry Flappy Bird. Stav aplikace je zachován při změnách v kódu. Zcela to změní způsob jak programujete.
Uvažme následující kód v Javě:
JFrame f = new JFrame();
JLabel l = new JLabel("Hello World");
l.setHorizontalAlignment(SwingConstants.CENTER);
f.add(l);
f.pack();
f.show();
Ten lze v Clojure přepsat do mnohem jasnější podoby:
(doto (JFrame.)
(.add (doto (JLabel. "Hello World")
(.setHorizontalAlignment SwingConstants/CENTER)))
.pack .show)
Více informací o Java interopu a Javascript interopu; npm balíky lze využívat pomocí Shadow-cljs.
Rich Hickey vytvořil v roce 2013.
Databáze naruby:
Rich Hickey zveřejnil v roce 2007, po dvou letech práce. Jméno Clojure vzniklo tak, že je to uzávěr (closure) jazyků C#, Java a Lisp (CLJ). Stabilní jazyk s malým jádrem. Existují skvělé knihovny a nástroje, které zvětšují produktivitu. Základní filozofie Clojuru je jednoduchost. |
Každý program potřebuje uchovávat stav, který se v čase mění. Clojure nemá proměnné, ale nabízí vyšší primitiva pro práci s hodnotami měnícími se v čase, podobně jako SQL databáze.
Clojure pracuje s hodnotami, které nelze změnit. Ale je možné z nich efektivně vytvořit modifikované hodnoty.
Rich Hickey vytvořil tyto datové struktury, aby byly dostatečně efektivní pro 99% všech aplikací. Rychlost čtení je 1-2x, rychlost zápisu 1-4x. Často může být program celkově efektivnější, což je kontraintuitivní.
Atom umožňuje pracovat s časem. Vždy ukazuje na nějakou hodnotu. Hodnoty jsou neměnné, ale u atom lze atomicky změnit, na jakou hodnotu bude odkazovat. Speciální funkce swap! a reset! mění hodnotu atomu.
(def a (atom {}))
@a ; => {}
(def b @a)
(swap! a assoc :abc 123)
@a ; => {:abc 123}
b ; => {}ClojureScript má pouze atomy, protože neběží paralelně. Clojure má i další primity: refs (podporují transakce), agents (asynchronní změna), vars.
Podporovány přímo jako primitiva v jazyku pomocí speciální notace:
'(1 2 "abc"), typicky pro kód, pomalý přístup k hodnotě na indexu.[1 2 :a :b "abc"], podporují přístup přes index.{:a 1 :b 23 "abc" 11}, dvojice klíč-hodnota, obojí může být cokoliv.#{:a :b "abc" 11 7}.Tyto datové struktury jsou imutabilní, velice efektivní, mohou obsahovat libovolné datové typy a lze je libovolně zanořovat.
{:source-paths ["src"]
:dependencies [[reagent "1.2.0"]
[re-frame "1.3.0"]
[binaryage/devtools "0.9.10"]]
:nrepl {:port 9000}
:builds {:client {:target :browser
:output-dir "resources/public/js/compiled"
:asset-path "/js/compiled"
:modules {:main {:init-fn example-client.core/init}}
:compiler-options {:infer-externs :auto}
:devtools {:http-root "resources/public"
:http-port 3000
:after-load example-client.core/mount-root
:watch-dir "resources/public"}}}}Dokumentace. Umožňuje nadefinovat lokální zkratky, což značně zjednoduší čitelnost a umožňuje použít hodnotu vícekrát. Ve většině situací nahrazuje lokální proměnné z ostatních jazyků. Syntaxe je
(let [dvojice symbol zkratky, hodnota]
libovolný seznam příkazů)
(let [[x y :as my-point] [5 3]]
(println x y)
(println my-point))
5 3
[5 3]
Sekvence je abstrakce, která má první prvek a zbytek sekvence. Se sekvencemi se pracuje líně, takže mohou být nekonečné. Například (range) je nekonečná sekvence přirozených čísel.
Všechny datové struktury Clojure lze používat jako sekvenci. Například mapa odpovídá sekvenci dvojic klíč-hodnota (v nějakém pořadí). Také datové struktury Javy fungují jako sekvence. Základní funkce Clojure skoro vždy pracují s libovolnou sekvencí, takže stejný kód funguje pro všechny datové struktury.
nil - odpovídá null.true/false - booleany.123, 14.23 - čísla.123M - BigInt (pouze Clojure) 2/3 - přesné zlomky (pouze Clojure)."abc" - řetězce.:abc - klíče, jakoby řetězce, ale identifikují sami sebe, typicky se používají jako klíče v mapách.abc - symbol, typicky odpovídá něčemu jinému.#"a.*$" - regulární výrazyInternet funguje na posílání textových zpráv. Formáty jako HTTP nebo JSON jsou použitelné pro kohokoliv. ORM a jiné binární zápisy nikoliv.
Idea Clojuru je, že můžeme použít stejné techniky pro programování vnitřku našich systému. Tedy jednotlivé časti spolu komunikují pomocí dat.
Data lze snadno zobrazit, zpracovat, fungují na ně stejné funkce, komunikace je transparentní. Na data se aplikují transformace, které je mění. Název funkcionální programování dává chybně do popředí transformace, důležitější jsou data.
Nepravdivé jsou hodnoty nil a false. Všechny ostatní hodnoty jsou pravdivé: 0, prázdné kolekce, atd. Negace pomocí funkce not.
Funkce =, not= porovnávají hodnoty, fungují pro libovolné datové struktury. Fungují pro libovolný počet parametrů.
Funkce <, <=, >, >= porovnávají čísla. Fungují pro libovolný počet parametrů: (< 1 2 3 4 5) je true.
Makra and a or přijímají libovolný počet argumentů. Vrací poslední/první pravdivou hodnotu. Například (or val 5) umožňuje definovat výchozí hodnotu, pokud by val byl nil.
(def beers (atom 0))
(defn beer-counter [{:keys [name]}]
[:div
[:h1 "Hello, " name]
[:p "You have drank " @beers " beers"]
[:button
{:on-click #(swap! beers inc)}
"Drink a beer"]])
Funkce se v Clojure definují pomocí defn:
(defn myfunc [x y z]
(+ (* x y) z))
Funkce se volají umístěním jména funkce a argumentů do seznamu:
(myfunc 2 3 4)
=> 10
Funkce jsou tzv. first-class, tedy lze je libovolně ukládat jako hodnoty nebo předávat jako argumenty.
Anonymní funkce se vytvoří pomocí fn:
(fn [x y]
(+ x y))
Pro velice jednoduché funkce existuje zkrácená notace
#(+ %1 %2)
Jednotlivé parametry jsou %1, %2, atd. Pro unární funkce lze použít % jako parametr.
EDN (Extensible Data Notation) je datový formát, ve kterém se programuje Clojure. Kompilátor Clojuru tento formát čte a interpretuje jako kód. EDN je vylepšený JSON, aby v něm šlo programovat. Není potřeba všude psát čárky, vedle řetězců podporuje i klíče a symboly. Lze také definovat nové datové typy pomocí tzv. reader literals.
Příkazy if, when a cond. Lze použivat kdekoliv, uvnitř libovolné formy, takže fungují jako čitelný operátor ?: z C.
If obsahuje podmínku, true formu a false formu:
(if (< x y)
x
y)
When je pouze if větev, může obsahoval libovolný počet forem.
(when (< x y)
(println "x is smaller")
x)
Cond umožnuje mít libovolně mnoho větví v elegantní syntaxi. První splněna větev vrátí svoji formu, zbytek se ignoruje:
(cond
(< x y) "x is smaller than y"
(< x z) "x is smaller than z"
:else "x is larger than both y and z")
Existují i užitečné zkratky if-not, when-not, if-let, when-let.
Přehled core funkcí Clojure a Clojurescriptu.
High-order funkce map transformuje jednotlivé prvky sekvence danou funkcí.
Tedy (x, y, z, ...) ⇒ (f(x), f(y), f(z), ...).
Funguje i pro více vstupních sekvencí.
(map inc [1 2 3 4]) ; => (2 3 4 5)
(map #(* %1 %2) [1 2 3 4] [0 1 2 3 4]) ; => (0 2 6 12)
Umožňují zřetězit seznam transformací, fungují jako pipeline. Tedy hodnota se pošle do první transformace, výsledek do druhé, atd. Více informací v dokumentaci.
(->> (range 10) ; => (0 1 2 3 4 5 6 7 8 9)
(map #(* % %)) ; => (0 1 4 9 16 25 36 49 64 81)
(filter even?) ; => (0 4 16 36 64)
(reduce +)) ; => 120
I'm sorry that I long ago coined the term "objects" for this topic because it gets many people to focus on the lesser idea. The big idea is "messaging"
"Je mi líto, že jsem kdysi dávno zavedl termín „objekty“ pro toto téma, protože to mnoho lidí vede k soustředění se na méně důležitou myšlenku. Tou hlavní myšlenkou je „zasílání zpráv“."
Když se řekne funkcionální programování, hned se většině programátorů vybaví Haskell. Ten je prorostlý v akademii, takže jim ho cpou na univerzitě. Osobně si myslím, že Haskell dělá funkcionánímu programování velkou škodu, protože je prezentováno jako záhadné a těžko pochopitelné. Slova jako teorie kategorií, monády nebo funktory vyvolávají u většiny programátorů následující reakci:
Clojure je praktický transparentní jazyk bez podobných nesmyslů.
Filtr ponechá pouze prvky splňující predikát (unární funkce) a remove je naopak odebere.
(filter odd? [0 1 2 3 4 5 6]) ; => (1 3 5)
(remove odd? [0 1 2 3 4 5 6]) ; => (0 2 4 6)
Oproti ostatním jazykům mnohem mocnější syntaxe, avšak pracuje nezávisle na prvcích sekvence. Vrací sekvenci transformovaných prvků.
(for [x [1 2 3 4 5]]
(* 2 x)) ; => (2 4 6 8 10)
(for [x (range 5)
y (range 5)
:when (< x y)
:let [z (+ x y)]]
[x y z])
; => ([0 1 1] [0 2 2] [0 3 3] [0 4 4] [1 2 3] [1 3 4]
; [1 4 5] [2 3 5] [2 4 6] [3 4 7])
Rich Hickey: Objekty jsou jako loutky a kdokoliv,
kdo má referenci, plně kontroluje objekt.
Takhle nefunguje reálný svět.
{:remote-addr "127.0.0.1",
:scheme :http,
:query-params {"somekey" "somevalue"},
:form-params {},
:request-method :get,
:query-string "somekey=somevalue",
:content-type nil,
:uri "/foobaz",
:server-name "localhost",
:params {"somekey" "somevalue"},
:headers {"accept-encoding" "gzip, deflate",
"connection" "close",
"user-agent" "Apache-HttpClient/4.1.2 (java 1.5)",
"content-length" "0",
"host" "localhost:8383"},
:content-length 0,
:server-port 8383,
:character-encoding nil}Funkce reduce. Prvky sekvence jsou postupně konzumovány a vytváří hodnotu. Jeden krok provádí binární funkce, která se aplikuje na předchozí nakumulovanou hodnout a následující prvek sekvence.
(reduce + (range 10)) ; => 45
(reduce conj #{} [1 3 7 4 8 3 1 7])
; => #{1 3 7 4 8}
For cyklus v jazycích jako C může mít mnoho rozdílných významů. V Clojure se používají odlišné funkce, takže význam je hned jasný.
for - jednotlivé hodnoty sekvence se transformují nezávisle, vypočet by mohl být paralelní.
for (int i = 0; i < 10; i++) { | (for [x arr] |
reduce - hodnoty jsou postupně kumulovány, jednotlivé běhy cyklu se ovlivňují.
int sum = 0; | (reduce + arr) |
Rich Hickey: This is life sucking … (Tohle vysává život …)