OrgPad logo

Programovací jazyk Clojure

Created by Pavel Klavík

#Clojure, #informatika

Programovací jazyk Clojure

JavaScript

ClojureScript je varianta jazyka, která se kompiluje do JavaScriptu. K tomu využíva Google Closure Compiler, který optimalizuje a produkuje minimální kód.

cljs-logo-120b

CLR

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

JVM

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.

Python a další

Existují i verze Clojure, které adresují další platformy.

Hostovaný jazyk na více platformách

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.

Příliš mnoho závorek

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

Založen na LISPu

Místo f(x,y,z) se píše (f x y z).

Neproblémy

Absence statické typové kontroly

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.

Využivaný stovkami velkých firem

Použivají ho firmy jako Apple, Facebook, Netflix, WalmartLabs, ...

Screenshot (151)

Hot code reloading

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.

Java(Script) interop

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.

Imutabilní databáze Datomic

datomic-logo-290x230Rich Hickey vytvořil v roce 2013.

Databáze naruby:

Clojure

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.

Clojure logo

Screenshot (150)

clojure-hopl-iv-final.pdf

TLDR: Proč používat Clojure

Rich Hickey - Are We There Yet?

STM primitiva pro paralelismus

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.

epochal-time-model

Efektivní imutabilní datové struktury

Clojure pracuje s hodnotami, které nelze změnit. Ale je možné z nich efektivně vytvořit modifikované hodnoty.

Screenshot (152)

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

Screenshot (153)

Rich Hickey - Clojure, Made Simple

Rich Hickey

rich

Atomy a další

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.

Datové struktury

Podporovány přímo jako primitiva v jazyku pomocí speciální notace:

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"}}}}

Rich Hickey - Simple Made Easy

Rich Hickey - Effective Programs - 10 Years of Clojure

let

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

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.

Základní datové typy

Základy jazyka

Datové programování

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

porovnávání

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.

Generování HTML v datech

(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

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

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.

when, if a cond

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.

Základní funkce a makra

Přehled core funkcí Clojure a Clojurescriptu.

Funkcionální programování

Screenshot (144)

map

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)

High-order funkce

Threading makra

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

Objektově orientované programování

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“."

Prokletí jménem Haskell

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:

fuck math

Clojure je praktický transparentní jazyk bez podobných nesmyslů.

filter a remove

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)

for

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

Ilustrace OOP

OOP visualised

Rich Hickey: Objekty jsou jako loutky a kdokoliv,
kdo má referenci, plně kontroluje objekt.
Takhle nefunguje reálný svět.

Screenshot (141)

HTTP Request jako data

{: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}

reduce

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}

odlišný význam

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++) {   
A[i] = 2 * A[i];
}
(for [x arr]
(* 2 x))

reduce - hodnoty jsou postupně kumulovány, jednotlivé běhy cyklu se ovlivňují.

int sum = 0;
for (int i = 0; i < 10; i++) {
sum += A[i];
}
(reduce + arr)

Objekt pro HTTP Request

Rich Hickey: This is life sucking … (Tohle vysává život …)

Screenshot (142)

data jsou mnohem jednodušší

Rich Hickey - Death by Specificity