Wenn Sie def verwenden können, um Variablen neu zu definieren, wie wird dies als unveränderlich angesehen?

10

Wenn Sie versuchen, Clojure zu lernen, müssen Sie ständig erfahren, wie es bei Clojure um unveränderliche Daten geht. Aber Sie können eine Variable einfach neu definieren, indem Sie defrichtig verwenden? Ich verstehe, dass Clojure-Entwickler dies vermeiden, aber Sie könnten vermeiden, Variablen in jeder Sprache trotzdem zu ändern. Kann mir jemand erklären, wie das anders ist, weil ich denke, dass mir das in den Tutorials und Büchern, die ich lese, fehlt.

Um ein Beispiel zu geben, wie es ist

a = 1
a = 2

in Ruby (oder Blub, wenn Sie es vorziehen) anders als

(def a 1)
(def a 2)

in Clojure?

Evan Zamir
quelle

Antworten:

9

Wie Sie bereits bemerkt haben, bedeutet die Tatsache, dass von Veränderlichkeit in Clojure abgeraten wird, nicht, dass dies verboten ist und dass es keine Konstrukte gibt, die dies unterstützen. Sie haben also Recht, dass defSie mit einer Bindung eine Bindung in der Umgebung ähnlich wie die Zuweisung in anderen Sprachen ändern / mutieren können (siehe die Clojure-Dokumentation zu vars ). Durch Ändern von Bindungen in der globalen Umgebung ändern Sie auch Datenobjekte, die diese Bindungen verwenden. Zum Beispiel:

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

Beachten Sie, dass sich nach der Neudefinition der Bindung für xauch die Funktion fgeändert hat, da ihr Körper diese Bindung verwendet.

Vergleichen Sie dies mit Sprachen, in denen die Neudefinition einer Variablen die alte Bindung nicht löscht, sondern nur schattiert , dh sie in dem Bereich unsichtbar macht, der nach der neuen Definition kommt. Sehen Sie, was passiert, wenn Sie denselben Code in die SML REPL schreiben:

- 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

Beachten Sie, dass xdie Funktion nach der zweiten Definition von fimmer noch die Bindung verwendet x = 1, die zum Zeitpunkt der Definition im Gültigkeitsbereich war, dh die Bindung val x = 100überschreibt die vorherige Bindung nicht val x = 1.

Fazit: Mit Clojure können Sie die globale Umgebung mutieren und Bindungen darin neu definieren. Es wäre möglich, dies zu vermeiden, wie es andere Sprachen wie SML tun, aber das defKonstrukt in Clojure soll auf eine globale Umgebung zugreifen und diese mutieren. In der Praxis ist dies sehr ähnlich zu dem, was die Zuweisung in imperativen Sprachen wie Java, C ++, Python tun kann.

Trotzdem bietet Clojure viele Konstrukte und Bibliotheken, die Mutationen vermeiden, und Sie können einen langen Weg zurücklegen, ohne sie überhaupt zu verwenden. Das Vermeiden von Mutationen ist bei weitem der bevorzugte Programmierstil in Clojure.

Giorgio
quelle
1
Das Vermeiden von Mutationen ist bei weitem der bevorzugte Programmierstil. Ich würde vorschlagen, dass diese Aussage heutzutage für jede Sprache gilt; nicht nur Clojure;)
David Arno
2

Bei Clojure dreht sich alles um unveränderliche Daten

Bei Clojure geht es darum , den veränderlichen Zustand durch Steuern der Mutationspunkte (dh Refs, Atoms, Agents und Vars) zu verwalten. Natürlich kann jeder Java-Code, den Sie über Interop verwenden, tun, was ihm gefällt.

Aber Sie können eine Variable einfach neu definieren, indem Sie def richtig verwenden?

Wenn Sie a Var(im Gegensatz zu z. B. einer lokalen Variablen) an einen anderen Wert binden möchten, dann ja. Wie in Vars und der globalen Umgebung erwähnt , werden Vars speziell als einer der vier "Referenztypen" von Clojure aufgenommen (obwohl ich sagen würde, dass sie sich dort hauptsächlich auf dynamische Var s beziehen ).

Mit Lisps gibt es eine lange Geschichte der Durchführung interaktiver, explorativer Programmieraktivitäten über die REPL. Dies beinhaltet häufig das Definieren neuer Variablen und Funktionen sowie das Neudefinieren alter Variablen und Funktionen. Außerhalb der REPL wird das Zurücksetzen von defa Varjedoch als schlechte Form angesehen.

Nathan Davis
quelle
1

Von Clojure für die Tapferen und Wahren

In Ruby können Sie beispielsweise einer Variablen mehrere Zuweisungen vornehmen, um ihren Wert aufzubauen:

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

Sie könnten versucht sein, in Clojure etwas Ähnliches zu tun:

(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!")))

Das Ändern des mit einem solchen Namen verknüpften Werts kann es jedoch schwieriger machen, das Verhalten Ihres Programms zu verstehen, da es schwieriger ist zu wissen, welcher Wert einem Namen zugeordnet ist oder warum sich dieser Wert möglicherweise geändert hat. Clojure verfügt über eine Reihe von Tools für den Umgang mit Änderungen, die Sie in Kapitel 10 kennenlernen werden. Wenn Sie Clojure lernen, werden Sie feststellen, dass Sie selten eine Namens- / Wertzuordnung ändern müssen. Hier ist eine Möglichkeit, den vorhergehenden Code zu schreiben:

(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!"
Tiago Dall'Oca
quelle
Könnte man in Ruby nicht einfach das Gleiche tun? Der Vorschlag besteht einfach darin, eine Funktion zu definieren, die einen Wert zurückgibt. Ruby hat auch Funktionen!
Evan Zamir
Ja, ich weiß. Aber anstatt einen zwingenden Weg zur Lösung des vorgeschlagenen Problems zu fördern (wie das Ändern von Bindungen), übernimmt Clojure das funktionale Paradigma.
Tiago Dall'Oca