Come già notato, il fatto che la mutabilità sia scoraggiata in Clojure non significa che sia vietato e che non ci siano costrutti che lo supportano.
Quindi hai ragione che usando def
puoi modificare / mutare un legame nell'ambiente in modo simile a ciò che l'assegnazione fa in altri linguaggi (vedi la documentazione di Clojure su vars ). Modificando i collegamenti nell'ambiente globale si cambiano anche gli oggetti dati che utilizzano questi collegamenti. Ad esempio:
user=> (def x 1)
#'user/x
user=> (defn f [y] (+ x y))
#'user/f
user=> (f 1)
2
user=> (def x 100)
#'user/x
user=> (f 1)
101
Notare che dopo aver ridefinito il binding per x
, anche la funzione f
è cambiata, poiché il suo corpo usa quel legame.
Confrontalo con linguaggi in cui ridefinire una variabile non cancella il vecchio binding ma solo shadows , cioè lo rende invisibile nell'ambito che viene dopo la nuova definizione. Guarda cosa succede se scrivi lo stesso codice in SML REPL:
- val x = 1;
val x = 1 : int
- fun f y = x + y;
val f = fn : int -> int
- f 1;
val it = 2 : int
- val x = 100;
val x = 100 : int
- f 1;
val it = 2 : int
Notare che dopo la seconda definizione di x
, la funzione f
usa ancora il binding x = 1
che era in ambito quando è stato definito, cioè il legame val x = 100
non sovrascrive il precedente legame val x = 1
.
Bottomline: Clojure consente di mutare l'ambiente globale e ridefinire i binding in esso. Sarebbe possibile evitare questo, come fanno altri linguaggi come SML, ma il costrutto def
in Clojure ha lo scopo di accedere e mutare un ambiente globale. In pratica, questo è molto simile a ciò che l'assegnazione può fare in linguaggi imperativi come Java, C ++, Python.
Ancora, Clojure fornisce molti costrutti e librerie che evitano le mutazioni, e puoi fare un lungo cammino senza usarlo affatto. Evitare la mutazione è di gran lunga lo stile di programmazione preferito in Clojure.