Если вы можете использовать def для переопределения переменных, как это считается неизменным?

10

Пытаясь выучить Clojure, вы не можете не говорить, как непрерывно рассказывает Clojure об неизменных данных. Но вы можете легко переопределить переменную, используя defправо? Я понимаю, что разработчики Clojure избегают этого, но вы можете избежать изменения переменных на любом языке. Может кто-нибудь объяснить мне, чем это отличается, потому что я думаю, что мне не хватает этого из учебников и книг, которые я читаю.

Чтобы привести пример, как это

a = 1
a = 2

в Ruby (или Blub, если вы предпочитаете) отличается от

(def a 1)
(def a 2)

в Clojure?

Эван Замир
источник

Ответы:

9

Как вы уже заметили, тот факт, что изменчивость в Clojure не рекомендуется, не означает, что она запрещена и что нет конструкций, поддерживающих ее. Таким образом, вы правы в том, что, используя, defвы можете изменять / изменять мутирование в среде так же, как это делает назначение на других языках (см. Документацию Clojure по vars ). Изменяя привязки в глобальной среде, вы также изменяете объекты данных, которые используют эти привязки. Например:

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

Обратите внимание, что после переопределения привязки xфункция fтакже изменилась, поскольку ее тело использует эту привязку.

Сравните это с языками, в которых переопределение переменной не удаляет старую привязку, а только затеняет ее, т. Е. Делает ее невидимой в области видимости, следующей за новым определением. Посмотрите, что произойдет, если вы напишите тот же код в 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

Обратите внимание, что после второго определения x, функция fвсе еще использует привязку, x = 1которая была в области действия, когда она была определена, то есть привязка val x = 100не перезаписывает предыдущую привязку val x = 1.

Итог: Clojure позволяет изменять глобальную среду и переопределять привязки в ней. Можно было бы избежать этого, как это делают другие языки, такие как SML, но defконструкция в Clojure предназначена для доступа и изменения глобальной среды. На практике это очень похоже на то, что назначение может делать в императивных языках, таких как Java, C ++, Python.

Тем не менее, Clojure предоставляет множество конструкций и библиотек, которые избегают мутаций, и вы можете пройти долгий путь, не используя его вообще. Избежание мутаций - безусловно предпочтительный стиль программирования в Clojure.

Джорджио
источник
1
Избегание мутаций - безусловно, предпочтительный стиль программирования. Я бы предположил, что это утверждение относится ко всем языкам в наши дни; не просто Clojure;)
Дэвид Арно
2

Clojure это все о неизменных данных

Clojure - это управление изменяемым состоянием путем управления точками мутации (т. Е. RefS, Atoms, Agents и Vars). Хотя, конечно, любой код Java, который вы используете через взаимодействие, может делать то, что пожелает.

Но вы можете легко переопределить переменную, используя def right?

Если вы хотите связать Var(в отличие, например, локальную переменную) с другим значением, тогда да. На самом деле, как отмечалось в Vars и Global Environment , Vars специально включены как один из четырех «ссылочных типов» Clojure (хотя я бы сказал, что они в основном ссылаются на динамические Var s там).

С Лиспом существует долгая история выполнения интерактивных, поисковых программ через REPL. Это часто включает определение новых переменных и функций, а также переопределение старых. Тем не менее, за пределами REPL, defa Varсчитается плохой формой.

Натан Дэвис
источник
1

От Clojure для Храбрых и Истинных

Например, в Ruby вы можете выполнить несколько присваиваний переменной для построения ее значения:

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

У вас может возникнуть соблазн сделать что-то подобное в 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!")))

Однако изменение значения, связанного с таким именем, может усложнить понимание поведения вашей программы, поскольку сложнее узнать, какое значение связано с именем или почему это значение могло измениться. Clojure имеет набор инструментов для работы с изменениями, о которых вы узнаете в главе 10. Изучая Clojure, вы обнаружите, что вам редко потребуется изменять связь имя / значение. Вот один из способов, которым вы могли бы написать предыдущий код:

(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!"
Тьяго Далл'Ока
источник
Разве нельзя легко сделать то же самое в Ruby? Данное предложение состоит в том, чтобы просто определить функцию, которая возвращает значение. В Ruby тоже есть функции!
Эван Замир
Да, я знаю. Но вместо того, чтобы поощрять императивный способ решения предложенной проблемы (например, изменение привязки), Clojure принимает функциональную парадигму.
Tiago Dall'Oca