Se puoi usare def per ridefinire le variabili, come viene considerato immutabile?


10

Stai cercando di imparare Clojure e non puoi fare a meno di sentirti continuamente informato su come Clojure sia tutto basato su dati immutabili. Ma puoi ridefinire facilmente una variabile usando defgiusto? Capisco che gli sviluppatori Clojure lo evitino, ma potresti evitare di cambiare le variabili in qualsiasi lingua lo stesso. Qualcuno può spiegarmi come sia diverso, perché penso che mi manchi dai tutorial e dai libri che sto leggendo.

Per fare un esempio com'è

a = 1
a = 2

in Ruby (o blub, se preferisci) diverso da

(def a 1)
(def a 2)

a Clojure?

Risposte:


9

Come hai già notato, il fatto che la mutevolezza sia scoraggiata in Clojure non significa che sia vietata e che non vi siano costrutti che la supportano. Quindi hai ragione defnel dire che usando puoi cambiare / mutare un legame nell'ambiente in modo simile a quello che il compito fa in altre lingue (vedi la documentazione di Clojure su vari ). Modificando i bind nell'ambiente globale si cambiano anche gli oggetti dati che usano questi bind. Per 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

Si noti che dopo aver ridefinito l'associazione per x, anche la funzione fè cambiata, poiché il suo corpo utilizza quell'associazione.

Confrontalo con le lingue in cui la ridefinizione di una variabile non cancella la vecchia rilegatura ma la ombreggia , cioè la rende invisibile nell'ambito che segue la nuova definizione. Vedi cosa succede se scrivi lo stesso codice nel REPL SML:

- 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

Si noti che dopo la seconda definizione di x, la funzione futilizza ancora l'associazione x = 1che era nell'ambito nell'ambito della definizione, ovvero l'associazione val x = 100non sovrascrive l'associazione precedente val x = 1.

Bottomline: Clojure permette di mutare l'ambiente globale e ridefinire i vincoli in esso. Sarebbe possibile evitarlo, come fanno altre lingue come SML, ma il defcostrutto in Clojure è pensato per accedere e mutare un ambiente globale. In pratica, questo è molto simile a quello che può fare un compito in linguaggi imperativi come Java, C ++, Python.

Tuttavia, Clojure offre molti costrutti e librerie che evitano la mutazione, e puoi fare molta strada senza usarla affatto. Evitare la mutazione è di gran lunga lo stile di programmazione preferito in Clojure.


1
Evitare la mutazione è di gran lunga lo stile di programmazione preferito. Suggerirei che questa affermazione si applica a tutte le lingue in questi giorni; non solo Clojure;)
David Arno

2

Clojure si basa su dati immutabili

Clojure riguarda la gestione dello stato mutabile controllando i punti di mutazione (cioè, Refs, Atoms, Agents, e Vars). Anche se, naturalmente, qualsiasi codice Java che usi tramite l'interoperabilità può fare ciò che desideri.

Ma puoi ridefinire facilmente una variabile usando def giusto?

Se intendi associare a Var(al contrario, ad esempio, una variabile locale) a un valore diverso, allora sì. In effetti, come osservato in Vars e nel Global Environment , gli Vars sono specificatamente inclusi come uno dei quattro "tipi di riferimento" di Clojure (anche se direi che si riferiscono principalmente a quelli dinamici Var ).

Con Lisps, esiste una lunga storia di attività interattive di programmazione esplorativa tramite REPL. Ciò comporta spesso la definizione di nuove variabili e funzioni, nonché la ridefinizione di quelle vecchie. Tuttavia, al di fuori del REPL, defil riferimento a Varè considerato una forma scadente.


1

Da Clojure per Brave and True

Ad esempio, in Ruby potresti eseguire più assegnazioni a una variabile per aumentarne il valore:

severity = :mild
  error_message = "OH GOD! IT'S A DISASTER! WE'RE "
  if severity == :mild
    error_message = error_message + "MILDLY INCONVENIENCED!"
  else
    error_message = error_message + "DOOOOOOOMED!"
  end

Potresti essere tentato di fare qualcosa di simile in Clojure:

(def severity :mild)
  (def error-message "OH GOD! IT'S A DISASTER! WE'RE ")
  (if (= severity :mild)
      (def error-message (str error-message "MILDLY INCONVENIENCED!"))
  (def error-message (str error-message "DOOOOOOOMED!")))

Tuttavia, la modifica del valore associato a un nome come questo può rendere più difficile la comprensione del comportamento del programma perché è più difficile sapere quale valore è associato a un nome o perché tale valore potrebbe essere cambiato. Clojure ha una serie di strumenti per affrontare il cambiamento, che imparerai nel Capitolo 10. Mentre impari Clojure, scoprirai che raramente dovrai modificare un'associazione nome / valore. Ecco un modo in cui potresti scrivere il codice precedente:

(defn error-message [severity]
   (str "OH GOD! IT'S A DISASTER! WE'RE "
   (if (= severity :mild)
     "MILDLY INCONVENIENCED!"
     "DOOOOOOOMED!")))

(error-message :mild)
  ; => "OH GOD! IT'S A DISASTER! WE'RE MILDLY INCONVENIENCED!"

Non si potrebbe facilmente fare la stessa cosa in Ruby? Il suggerimento dato è semplicemente quello di definire una funzione che restituisce un valore. Anche Ruby ha delle funzioni!
Evan Zamir,

Si, lo so. Ma invece di incoraggiare un modo imperativo di risolvere il problema proposto (come cambiare i vincoli), Clojure adotta il paradigma funzionale.
Tiago Dall'Oca,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.