Category Archives: Clojure

Clojure, concetti fondamentali XI

c2

Interoperatività con Java
new e . sono le special form per l’interoperatività con Java. Peraltro ci sono primitive per rendere più conciso e sintatticamente più vicino a Clojure queste forme di istanziazione. È quindi raro vederli usati direttamente ma prima o poi capita di trovarle.

t1

Gestione delle Eccezioni: try e throw
Queste forme speciali consentono di accedere alla gestione delle eccezioni di Java da Clojure. E meriterebbero un capitolo tutto per loro.

Specialized Mutation: set!
Lasciato in inglese perché troppo specialistico. Clojure insiste sull’immutabilità dei dati, ma poi capita e allora c’è set! che può essere usato per:

  • definire il valore di un thread locale;
  • settare il valore di un campo Java;
  • settare il valore di un campo mutabile definito con settype.

Primitive di lock: monitor-enter e monitor-exit
Queste primitive consentono di sincronizzarsi con Java. Però non è necessario usarle, c’è la macro locking.

E quindi…

A questo punto mi fermo. Ho acquisito un’idea di cosa è Clojure, quali sono le sue potenzialità e come può essere usato. Ecco questo è il motivo per cui non proseguo: bisognerebbe avere un caso adatto da sviluppare; probabilmente sul Web.
Chissà…

c5

Non so quando ne riparlerò ma forse questo è il futuro della programmazione, anche se:

Prediction is very difficult, especially about the future.

(cit.), (Niels Bohr).

Clojure, concetti fondamentali X

c1

if

if è il solo operatore condizionale in Clojure. La forma è: se la prima espressione è logicamente true allora la seconda espressione è valutata e il suo valore ritornato. Altrimenti il risultato è quello della terza espressione. Sia la seconda che la terza espressione vengono valutate solo se necessario.

Per Clojure è vero tutto quello che non è nil o false:

i0

Come si vede se la condizione è falsa e non c’è l’else il risultato è nil.

Da if derivano numerose istruzioni specializzate:

  • when, utile quando dev’essere ritornato nil se la condizione è false;
  • cond, simile a else if di Java e elif di Python;
  • if-let e when-let, composizione rispettivamente di if e when con let. Se il valore è vero viene collegato a un for locale per l’estensione dell’espressione then.

Notare che i predicati true? e false? non sono collegati a if:

i1

true? e false? verificano per true e false, non per la condizione logica true usata da if che è equivalente a (or (not (nil? x)) (true? x)):

i2

Cicli: loop e recur

Clojure ha diversi cicli, come doseq e dotimes costruiti tutti su recur. recur trasferisce il controllo al loop immediatamente precedente senza consumare lo stack. Esempio (countdown):

i3

che naturalmente può essere scritta molto meglio, così:

i4

(devo ancora prenderci la mano ;-))

Uso di recur: recur è un loop a basso livello e la ricorsione non è necessaria quando:

  • quando possibile usare doseq e dotimes;
  • per iterare su una collezione o sequenza è preferibile usare map, reduce, for

Siccome recur non consuma lo stack (e quindi evita gli errori di stack overflow) è adatto a algoritmi ricorsivi. Inoltre è ottimo per operazioni con molti calcoli.
Poi ci sono casi più complessi in cui usando solo map, reduce e simili diventa difficoltoso o inefficiente. In questi casi recur è fondamentale.

Tanto per fare scena guarda qua:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
(ns clojureprogramming.mandelbrot
    (:import java.awt.image.BufferedImage
        (java.awt Color RenderingHints)))

(defn- escape
    "Returns an integer indicating how many iterations were required
     before the value of z (using the components `a` and `b`) could
     be determined to have escaped the Mandelbrot set. If z
     will not escape, -1 is returned."
    [a0 b0 depth]
    (loop [a a0
           b b0
           iteration 0]
    (cond
        (< 4 (+ (* a a) (* b b))) iteration
        (>= iteration depth) -1
        :else (recur (+ a0 (- (* a a) (* b b)))
                     (+ b0 (* 2 (* a b)))
                     (inc iteration)))))

(defn mandelbrot
    "Calculates membership within and number of iterations to escape
     from the Mandelbrot set for the region defined by `rmin`, `rmax`
     `imin` and `imax` (real and imaginary components of z,          respectively).
     Optional kwargs include `:depth` (maximum number of iterations
     to calculate escape of a point from the set), `:height` ('pixel'
     height of the rendering), and `:width` ('pixel' width of the
     rendering).

     Returns a seq of row vectors containing iteration numbers for when
     the corresponding point escaped from the set. -1 indicates points
     that did not escape in fewer than `depth` iterations, i.e. they
     belong to the set. These integers can be used to drive most common
     Mandelbrot set visualizations."
    [rmin rmax imin imax & {:keys [width height depth]
                            :or {width 80 height 40 depth 1000}}]
    (let [rmin (double rmin)
        imin (double imin)
        stride-w (/ (- rmax rmin) width)
        stride-h (/ (- imax imin) height)]
    (loop [x 0
           y (dec height)
           escapes []]
    (if (== x width)
        (if (zero? y)
            (partition width escapes)
            (recur 0 (dec y) escapes))
        (recur (inc x) y (conj escapes (escape (+ rmin (* x stride-w))
                                               (+ imin (* y stride-h))
                                               depth)))))))

    (defn render-text
        "Prints a basic textual rendering of mandelbrot set membership,
         as returned by a call to `mandelbrot`."
        [mandelbrot-grid]
        (doseq [row mandelbrot-grid]
            (doseq [escape-iter row]
                (print (if (neg? escape-iter) \* \space)))
            (println)))

    (defn render-image
        "Given a mandelbrot set membership grid as returned by a call to                                            
         `mandelbrot`, returns a BufferedImage with the same resolution as the
         grid that uses a discrete grayscale color palette."
        [mandelbrot-grid]
        (let [palette (vec (for [c (range 500)]
                (Color/getHSBColor 0.0 0.0 (/ (Math/log c) (Math/log 500)))))
            height (count mandelbrot-grid)
            width (count (first mandelbrot-grid))
            img (BufferedImage. width height BufferedImage/TYPE_INT_RGB)
            ^java.awt.Graphics2D g (.getGraphics img)]
            (doseq [[y row] (map-indexed vector mandelbrot-grid)
                   [x escape-iter] (map-indexed vector row)]
                (.setColor g (if (neg? escape-iter)
                (palette 0)
                (palette (mod (dec (count palette)) (inc escape-iter)))))
            (.drawRect g x y 1 1))
        (.dispose g)
        img))

(render-text (mandelbrot -2.25 0.75 -1.5 1.5 :width 80 :height 40 :depth 100))

mand

Clojure, concetti fondamentali IX

c0
Naturalmente si continua sempre da qui.

Argomenti keyword

Spesso capita di dover definire funzioni con argomenti di default, oppure senza un ordine predefinito. fn (e quindi defn) lo consentono attraverso keyword arguments, coppie di keyword e valori nella chiamata e se la definizione della funzione è appropriata queste coppie vengono destrutturate e piazzate nella giusta posizione, come rest arguments. Esempio:

k0

Come si vede la funzione richiede un solo argomento: username, tutti gli altri sono copie chiave-valore, dopo la &.
Come valore di default per join-date viene definito now.

Pre- e post-condizioni
fn li consente, ma saranno trattati prossimamente.

Function literals
Sono l’equivalente dei blocchi in Ruby e lambda in Python. Tornano utili quando serve definire una funzione anonima, specie se molto semplice. Per esempio queste funzioni anonime sono equivalenti:

(fn [x y] (Math/pow x y))

#(Math/pow %1 %2)

Quest’ultima viene espansa nella precedente, come si può vedere:

k1

Ci sono differenze fra le due forme.

Form do implicito:
la form fn ha un do implicito attorno al corpo (body) della funzione, esempio:

(fn [x y]
    (println (str x \^ y))
    (Math/pow x y))

l’equivalente letterale richiede il do

#(do (println (str %1 \^ %2))
    (Math/pow %1 %2))

Arità e argomenti specificati usando simboli senza nome:
come si vede dagli esempi precedenti la fn usa i simboli x e y mentre la letterale usa simboli senza nome passati con %, il primo %1, il secondo %2 e così via. Inoltre il valore più grande definisce l’arità (numero di parametri).
Il primo argomento %1 può essere scritto semplicemente %. E si può avere un numero variabile di argomenti con &%, per cui queste sono equivalenti:

(fn [x & rest]
    (- x (apply + rest)))

#(- % (apply + %&))

Le literals function non possono essere annidate:
questa è OK:

(fn [x]
    (fn [y]
        (+ x y)))

ma questa no:

#(#(+ % %))

k2

(per fortuna!).

Pausa? OK 😉 😀

Clojure, concetti fondamentali VIII

rich-hickey-function

Creare funzioni: fn

Le funzioni sono valori first-class creati dalla special form fn che racchiude anche let e do.

Ecco un esempio, una funzione che aggiunge 10 all’argomento passatole

(fn [x]
  (+ 10 x))

Notare che gli argomenti vengono passati in un vettore. Tutto quello che segue è il corpo della funzione, c’è un do implicito. L’ultima form eseguita è il valore ritornato dalla funzione.

La valutazione degli argomenti:

f0

8 in questo caso è il solo argomento e viene collegato a x; è equivalente a questa form:

f1

Si possono avere più argomenti:

f2

equivalente a:

f3

Si possono creare anche funzioni con multiple arities (numero di parametri). In questo caso si mette la funzione in un var per poterla chiamare per nome:

f4

In questo caso ogni arity dev’essere racchiusa tra una coppia di parentesi, la form giusta viene scelta in base al numero di parametri passato.

defn

defn è una macro che incapsula def e fn in modo da poterla definire con nome in forma concisa. Per esempio le due definizioni seguenti sono equivalenti:

(def strange-adder (fn adder-self-reference
                     ([x] (adder-self-reference x 1))
                     ([x y] (+ x y))))

equivalente a:

(defn strange-adder
    ([x] (strange-adder x 1))
    ([x y] (+ x y)))

f6

Per le funzioni con una sola arity non serve la  coppia di parentesi addizionali, per cui si ha:

(def redundant-adder (fn redundant-adder
                       [x y z]
                       (+ x y z)))

e

(defn redundant-adder
    [x y z]
    (+ x y z))

L’uso di defn semplifica il codice in qundo non è necessario continuare a ridefinire ogni volta la funzione.

Destrutturazione degli argomenti

Usando let si può destrutturare gli argomenti usati dalla funzione. Ci sono diversi casi, eccone un paio.

Funzioni variadic
Sono le funzioni con variabili opzionali, così le chiama Java. In questo caso si una una seq, esempio:

f9

Si può usare anche per gestire la zero-arity (no args), esempio (mio, vediamo se mi riesce di modificare il precedente):

f10

Uh! ancora troppe cose; pausa 😉

Clojure, concetti fondamentali VII

justin

Destrutturazione di mappe

È come la destrutturazione sequenziale; si usa con

  • hash-maps, array-maps e records (prossimamente ne parlo);
    le collezioni che implementano java.util.Map;
    ogni valore che supporta get.

Ecco un esempio elementare:

m0

Con la stessa mappa m si possono fare le seguenti operazioni

m1

notare che non è necessario che la chiave sia una keyword (quelle che iniziano con due-punti).

Gli indici in vettori, stringhe e arrays possono essere usati.

m2

Poi si possono destrutturare forme composte, esempio:

m3

dove si è usata la mappa iniziale. ma cos’è successo? La form esterna {{e :e} :d} agisce su m per liberarci del valore mappato in d; quella interna {e :e} estrae il valore mappato in e.

difatti:

m5

E si può arrivare a cose come questa:

m6

e anche

m8

Come per la sequenziale :as viene usato per conservare il riferimento alla collezione originaria:

m9

(no non ci ho capito niente, ci devo pensare su).

Con :or è possibile assegnare valori di default per quelle chiavi che non sono disponibili:

m10

questo consente di non dover fare il merging della mappa sorgente prima di destrutturarla, vale anche per il caso in cui la chiave non ci sia:

m11

Inoltre :or distingue tra valore non definito e falso (nil e false):

m12

Spesso ci sono nomi stabili per vari valori nelle mappe e è utile usare lo stesso nome all’interno del let. Fare questo però risulta ripetitivo:

m13

Dai, noioso! In questi casi si possono usare :keys, :strs e :syms per specificare chiavi di tipo keyword, stringa e simbolo nella mappa sorgente e i nomi corrispondenti ai valori possono essere collegati nella form let senza ripetizioni. Esempio (usando :keys):

m14

oppure possiamo usare :strs, così:

m15

e con :syms diventa:

m16

Come già visto in precedenza possiamo trattare valori in più come una seq “rest”. Ecco un semplice caso di un vettore che contiene qualche valore posizionale seguito da coppie chiave/valore:

m17

Il modo manuale sarebbe accettabile, questo:

m18

come si vede abbiamo destrutturato il vettore nei suoi elementi posizionale e i resto in una rest seq costruita ca coppie chiave/valore che possiamo usare come vogliamo.
Ma si può fare di meglio, sempre che il rest sua fatto da coppie chiave/valore:

m19

OK? 😉 Sì, panico 😮 (quasi).

Nota comica: se uno scrive due-punti+or ottiene questo :or. Sono dovuto ricorrere a <code>:</code>or<code></code>. 😉

Clojure, concetti fondamentali VI

fakerichhickey

Destrutturazione (destructuring)

Parecchia programmazione in Clojure consiste nell’operare con strutture di dati, come le collezioni di tipo sequenziali e mappe. Diverse funzioni accettano e ritornano seq e map e parecchie librerie sono costruite per trattare queste strutture astratte invece di casi specifici. Sorge allora la necessità di accedere facilmente ai valori di queste collezioni, esempio:

d0
OK, autoevidenti first, second, last e nth. E siccome i vettori sono funzioni si può scrivere (v 2). E si può usare .get perché tutte le collezioni sequenziali implementano java.util.List.

E si possono fare cose come queste:

d1

Ci sono due tipi di destrutturazione: una per le collezioni sequenziali e una per le mappe.

Strutture sequenziali

la destrutturazione sequenziale funziona con

  • liste, vettori e seqs
  • qualsiasi collezione che implementa java.util.List, come ArrayLists e LinkedLists
  • array Java
  • stringhe, scomposte nei loro caratteri

Un esempio elementare con lo stesso vettore v di prima:

d2

qui abbiamo costruito un vettore di simboli —[x y z]— e lo abbiamo usato invece dei loro valori. Siccome viene destrutturato sequenzialmente in x c’è il primo elemento (o dovrei dire zeresimo), in y il successivo e così via.

Python ha l’unpacking che fa la stessa cosa:

d3

Notare che devo fornire anche a, difatti

d4

E con Ruby? Ecco:

d5

Tornando a noi:

d6

possiamo usare _ per i valori che non interessano, difatti:

[42 "foo" 99.2 [5 12]]
[x  _     _    [y z ]]

Ma non è finita qui: ci sono ancora due possibilità.

È possibile recuperare valori sequenziali oltre la posizione definita con l’operatore &, simile a varargs di Java.

d7

particolarmente utile in caso di ricorsività o loop. da notare che rest non è un vettore anche se la form da destrutturare lo era.

È possibile conservare il valore destrutturato specificando l’opzione :as, esempio:

d8

OK, basta con la destrutturazione delle collezioni sequenziali.
E per le mappe? Uh! saranno l’argomento del prossimo post 😉

Clojure, concetti fondamentali V

Clojure_300x300_normal

Blocchi di codice: do

do esegue tutte le espressioni che gli vengono passate e ritorna il valore dell’ultima (progn del Lisp classico e begin di newLISP, esempio:

d0

Da notare che molte altre forme (fn, let, loop, try e le loro derivate come defn) racchiudono un do implicito nel loro corpo. Per esempio let:

d1

Definire vars: def

def definisce, o ridefinisce, una var nel namespace corrente

d2

Ci sono molte altre form per creare o ridefinire var, di solito il nome inizia con def: defn, defn-, defprotocol, defonce, defmacro, …
Però non tutte quelle che iniziano con def creano var: deftype, defrecord, defmethod.

Assegnazioni locali: let

let definisce var che locali all’espressione let stessa. Per esempio questo metodo (rudimentale) in Java:

public static double hypot (double x, double y) {
    final double x2 = x * x;
    final double y2 = y * y;
    return Math.sqrt(x2 + y2);
}

in Clojure diventa:


(defn hypot
    [x y]
    (let [x2 (* x x)
          y2 (* y y)]
        (Math/sqrt (+ x2 y2))))

x2 e y2 sono locali, definite solo all’interno del let. E similmente x e y sono locali a defn (come detto poche righe sopra).
Una nota per me: devo abituarmi, come qui, a scrivere il vettore degli argomenti su una riga a se, è infatti possibile inserire dopo il nome della funzione un commento esplicativo, come in questo caso:

d3

let ha un paio di caratteristiche semantiche molto diverse dai normali linguaggi:

1. Tutte le var locali sono immutabili. Si possono ridefinire con un let annidato ma non è possibile cambiarne il valore all’interno del let, cosa che elimina errori molto comuni.
Le forme speciali loop e recur vengono usate per il caso in cui i valori debbano cambiare (prossimamente).
Se davvero serve una variabile locale variabile c’è tutta una serie di form (prossimamente).

2. Il vettore di let è interpretato in fase di compilazione, per permettere il destructuring, cosa che consente di eliminare certi tipi di codice lunghi e noiosi.

Ecco qui ci sarebbe da parlare del destructuring, discorso troppo lungo, pausa 😉

Clojure, concetti fondamentali IV

960-RichHickey

Valutazione dei simboli

Adesso che conosciamo i namespaces possiamo tornare al precedente esempio di calcolo della media, presentato qui, che è poi questa:

(defn average [numbers]
    (/ (apply + numbers) (count numbers)))

Esaminiamo il corpo di questa funzione: ci sono diversi simboli che si riferiscono o a una var attiva nel namespace corrente o a un valore locale:

  • /, apply, + e count sono funzioni definite nel namespace clojure.core;
  • numbers è sia il solo argomento passato alla funzione che il valore usato nel corpo in (apply + numbers) e (count numbers).

(average [60 80 100 400])

Quando poi si chiama la funzione average con il vettore il simbolo average si riferisce al valore di #'(average) (nota per me che me ne dimentico sempre #'sym è la var per il simbolo sym) nel namespace corrente.

Forme speciali

Non considerando l’interoperabilità con Java i simboli nella function position (i car nelle liste per dirla lispescamente) possono essere di due tipi:

  • il valore di una var relativa a un namespace nominato o locale, come già visto;
  • una special form di Clojure.

Le special form sono i componenti fondamentali (i pezzi del Lego) con i quali il resto di Clojure è costruito. Concetto che risale alle origini del Lisp (a me viene da pensare alle macro). In ogni caso Clojure è costruito sulle special form, bisogna rassegnarsi a conoscerle, una per una.

Soppressione della valutazione: quote

quote sopprime la valutazione dell’espressione, per esempio per i simboli. Con quote allora i simboli rappresentano se stessi, come le stringhe, i numeri, …:

s0

Naturalmente scrivere quote è troppo lungo, fin dalle origini del Lisp c’è il carattere quote ' (apice, apostrofo).

s1

Ogni form può essere quotata, esempio:

s2

Le liste sono valutate come chiamate, quotandole se ne sopprime la valutazione, ottenendo la lista stessa, costituita in questo caso dai simboli '+, 'x e 'x. Che è poi quello che facciamo quando costruiamo la lista:

s3

Quotando una form si vede cosa produce il reader, p.es.:

s4

OK, l’ultima scritta così non è che si capisca molto, meglio in questa forma:

'`(a b ~c)
(clojure.core/seq
    (clojure.core/concat (clojure.core/list (quote user/a))
    (clojure.core/list (quote user/b))
    (clojure.core/list c)))

e se poi elimino il namespace comune diventa:

'`(a b ~c)
(seq (concat
    (list (quote user/a))
    (list (quote user/b))
    (list c)))

Basta! Clojure va preso a piccole dosi; sarà lunga 😉

Clojure, concetti fondamentali III

Sì, continuo da qui e qui, ancora sui componenti fondamentali del linguaggio.

220px-Rich_Hickey

Namespaces

Esaminiamo cosa fa REPL (e quindi Clojure):

  • Read: il reader legge una rappresentazione testuale del codice e produce strutture dati, cioè liste, vettori, simboli, numeri stringhe, etc;
  • Evaluate: parecchi dei valori prodotti dal reader vengono valutati pe se stessi come i numeri, le stringhe e le keyword. Le liste abbiamo già visto come vengono valutate.

Resta da vedere come vengono valutati i simboli. La semantica della loro valutazione è legata ai namespaces, l’unità fondamentale della modularità in Clojure.
Tutto il codice in Clojure è definito e valutato all’interno di un namespace, l’equivalente di un modulo in Python o un package in Java.

Le vars (locazioni di memoria mutabili) sono associate a un simbolo nel namespace in cui sono definite. Sono definite usando la special form def, che opera solo nel namespace corrente. Definiamo adesso la var x nel namespace user (quello di default); il nome della var è il simbolo che è codificato nel namespace corrente:

ns0

possiamo accedere al valore di questa var usando questo simbolo:

ns1

il simbolo x non è qualificato per cui è assunto nel namespace corrente. Possiamo anche ridefinirlo:

ns2

I simboli possono essere namespace-qualified, nel qual caso vengono risolti in quello specifico namespace invece che nel corrente:

ns3

*ns* restituisce, come si vede dall’esempio, il namespace corrente.

I namespaces vedono anche le classi Java; tutte le classi di java.lang sono importate di default e possono così essere riferiti senza qualificarli; per riferire una classe non importata si deve qualificare. Ogni simbolo che nomina una classe viene valutato in quella:

ns4

Inoltre tutti i namespaces hanno un alias  nel namespace primario della libreria standard di Clojure, clojure.core. Per esempio la c’è la funzione filter, definita in clojure.core che può essere riferita senza qualificarla:

ns5

Ma per i namespaces ci sarebbe ancora altro da aggiungere. Prossimamente.

Come in un prossimo post continuerà questo noioso esame dei concetti fondamentali, forse 😉

Clojure, concetti fondamentali II

Lambda

Continuo da qua: Clojure, concetti fondamentali I nell’esame dei componenti fondamentali del linguaggio.

Numeri
Clojure prevede millemila tipi di numeri, questi:

esempio tipo numerico
42, 0xff, 2r111, 040 long (64-bit segnato)
3.14, 6.0221415e23 double (64-bit IEEE floating point decimal
42N clojure.lang.BigInt (intero, precisione arbitaria)
0.01M java.math.BigDecimal (floating point
segnato con precisione arbitraria)
22/7 clojure.lang.Ratio

Ogni numero può essere negativo, con il iniziale.
I numeri in notazione esadecimale sono come nel C: 0xff vale 255 e 0xd055 è 53333.
Idem per gli ottali 040 vale 32 decimale.
Ci sono anche i numeri (interi) a base flessibile, nella forma BrN dove B è la base, compresa tra 2 e 36. Per esempio 2r111 è 7 e 16rff 255.
I ratios, numeri razionali sono sempre rappresentati da due interi separati da /.

Espressioni regolari
Clojure tratta le stringhe con prefisso # come espressioni regolari:

s1

che è quasi come /.../ di Ruby:

s2

nota: irq=’irb –simple-prompt’

A differenza di Java non si deve escapare il backslash

s3

in Java si sarebbe dovuto scrivere “(\\d+)-(\\d+)“.

Commenti
Ci sono due tipi di commento:

  • ; quello usuale del Lisp, dal punto-virgola fino alla fine della riga;
  • #_ a livello di forma, utile per il debug, eccone un esempio:

s4

C’è poi la macro comment:

s5

può fallire:

s6

perché torna nil, usare #_ in questi casi.

Spazi (whitespaces) e virgole
Di solito le virgole, usuali negli altri linguaggi di programmazione, non ci sono:

(defn somma-ingenua
    [x y]
    (+ x y))

Questo perché lo spazio (o tab o a-capo) è sufficiente a separare le variabili. Ma se uno vuole può mettere anche le virgole (fin dalle origini del Lisp, poi qualcuno non le accetta ma è un altro discorso).

(defn somma-ingenua
    [x, y]
    (+ x, y))

E vale anche questo:

s7

Ci sono dei casi in cui le virgole tornano utili per migliorare la leggibilità, come nelle mappe:

(create-user {:name new-username, :email email})

Collezioni
Esempi delle strutture di dati più comuni:

'(a b :name 12.5)      ;; lista
['a 'b :name 12.5]     ;; vettore
{:name "Chas" :age 31} ;; mappa
#{1 2 3}               ;; insieme (set)

Notare che la lista dev’essere quotata (con l’apice ') perché altrimenti viene eseguita.

Scorciatoie per espressioni
Ce ne sono parecchie, difficili da memorizzare per me:

una funzione anonima può essere definita con #();
le var possono essere riferite con #' (vedi esempio sotto)
l’istanza a una referenza può essere dereferenziata con @ (vedi esempio sotto)
per le macro ci sono ` syntax-quote, ~ unquote e ~@ unquote splicing (prossimamente…).
ogni struttura può avere metadata, piccole quantità di informazione, come per indicare il tipo di valore ritornato da una funzione o se è privata per un certo namespace, si usa ^

A proposito di #'

s8

e di @

s9

OK, si capisce poco, prossimamente…

No, non è finita, da continuare, ancora 😉