Якщо ви можете використовувати def для перевизначення змінних, як це вважається незмінним?


10

Намагаючись навчитися Clojure, ви не можете допомогти, але постійно говорять про те, як Clojure стосується незмінних даних. Але ви можете легко змінити змінну за допомогою defправа? Я розумію, що розробники Clojure уникають цього, але ви можете уникнути зміни змінних на будь-якій мові так само. Чи може хтось мені пояснити, як це все відрізняється, бо я думаю, що мені цього не вистачає з підручників та книжок, які я читаю.

Навести приклад, як це

a = 1
a = 2

в Ruby (або, якщо ви хочете), відрізняється від

(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також змінилася, тому що її організм використовує це зв'язування.

Порівняйте це з мовами, в яких переосмислення змінної не видаляє старе прив'язування, а лише затінює його, тобто робить його невидимим у тій області, що надходить після нового визначення. Подивіться, що станеться, якщо ви запишете той самий код у відповідь 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, який ви використовуєте через interop, може робити як завгодно.

Але ви можете легко змінити змінну, використовуючи def прав?

Якщо ви маєте на увазі прив’язати a Var(на відміну, наприклад, локальної змінної) до іншого значення, то так. Насправді, як зазначається у Vars та Global Environment , Vars спеціально включаються як один із чотирьох "посилальних типів" Clojure (хоча я б сказав, що вони, головним чином, мають на увазі динамічні Var системи).

У Lisps існує довга історія здійснення інтерактивних, дослідницьких програмних програм через 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!"

Хіба не можна було легко зробити те ж саме в Рубі? Надана пропозиція - просто визначити функцію, яка повертає значення. У Рубі теж є функції!
Еван Замір

Так, я знаю. Але замість того, щоб заохочувати імперативний спосіб вирішення запропонованої проблеми (як-от зміна прив’язок), Clojure приймає функціональну парадигму.
Tiago Dall'Oca
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.