Jeśli możesz użyć def do przedefiniowania zmiennych, jak to się uważa za niezmienne?

10

Próbujesz nauczyć się Clojure, a nie możesz nie wiedzieć, jak Clojure jest o niezmiennych danych. Ale możesz łatwo zredefiniować zmienną, używając defprawa? Rozumiem, że programiści Clojure unikają tego, ale można uniknąć zmiany zmiennych w dowolnym języku tak samo. Czy ktoś może mi wyjaśnić, jak to jest inaczej, ponieważ myślę, że brakuje mi tego w samouczkach i książkach, które czytam.

Aby podać przykład, jak to jest

a = 1
a = 2

w Ruby (lub blub, jeśli wolisz) różni się od

(def a 1)
(def a 2)

w Clojure?

Evan Zamir
źródło

Odpowiedzi:

9

Jak już zauważyłeś, fakt, że w Clojure odradza się zmienność, nie oznacza, że ​​jest zabroniony i nie ma żadnych konstrukcji, które by to wspierały. Masz więc rację, że używając defmożesz zmienić / mutować powiązanie w środowisku w sposób podobny do przypisania w innych językach (zobacz dokumentację Clojure na temat vars ). Zmieniając powiązania w środowisku globalnym, zmieniasz także obiekty danych korzystające z tych powiązań. Na przykład:

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

Zauważ, że po ponownym zdefiniowaniu wiązania xfunkcja frównież się zmieniła, ponieważ jego ciało używa tego wiązania.

Porównaj to z językami, w których redefinicja zmiennej nie usuwa starego powiązania, a jedynie zacienia go, tzn. Czyni go niewidocznym w zakresie, który pojawia się po nowej definicji. Zobacz, co się stanie, jeśli napiszesz ten sam kod w 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

Zauważ, że po drugiej definicji xfunkcja fnadal używa powiązania, x = 1które było w zasięgu, gdy zostało zdefiniowane, tzn. Powiązanie val x = 100nie zastępuje poprzedniego powiązania val x = 1.

Konkluzja: Clojure pozwala mutować globalne środowisko i redefiniować w nim powiązania. Można by tego uniknąć, tak jak robią to inne języki, takie jak SML, ale defkonstrukcja w Clojure ma na celu uzyskiwanie dostępu i mutowanie środowiska globalnego. W praktyce jest to bardzo podobne do tego, co przypisanie może zrobić w imperatywnych językach, takich jak Java, C ++, Python.

Mimo to Clojure zapewnia wiele konstrukcji i bibliotek, które unikają mutacji, i możesz przejść długą drogę, nie używając go wcale. Unikanie mutacji jest zdecydowanie preferowanym stylem programowania w Clojure.

Giorgio
źródło
1
Unikanie mutacji jest zdecydowanie preferowanym stylem programowania. Sugerowałbym, aby to stwierdzenie dotyczyło obecnie każdego języka; nie tylko Clojure;)
David Arno
2

Clojure to niezmienne dane

Clojure polega na zarządzaniu stanem zmiennym poprzez kontrolowanie punktów mutacji (tj. RefS, Atoms, Agents i Vars). Chociaż oczywiście każdy kod Java, którego używasz przez interop, może robić, co chcesz.

Ale możesz łatwo przedefiniować zmienną za pomocą def, prawda?

Jeśli masz na myśli powiązanie Var(w przeciwieństwie np. Zmiennej lokalnej) z inną wartością, to tak. W rzeczywistości, jak zauważono w Vars i globalnym środowisku , Varsą one specjalnie uwzględnione jako jeden z czterech „typów referencyjnych” Clojure (choć powiedziałbym, że odnoszą się głównie do dynamicznych Var ).

W Lisps istnieje długa historia wykonywania interaktywnych, eksploracyjnych działań programistycznych za pośrednictwem REPL. Często wiąże się to z definiowaniem nowych zmiennych i funkcji, a także redefiniowaniem starych. Jednak poza REPL, ponownie defing Varjest uważany za słabą formę.

Nathan Davis
źródło
1

Od Clojure for the Brave and True

Na przykład w Rubim możesz wykonać wiele przypisań do zmiennej, aby zbudować jej wartość:

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

Możesz mieć ochotę zrobić coś podobnego w 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!")))

Jednak zmiana wartości powiązanej z taką nazwą może utrudnić zrozumienie zachowania twojego programu, ponieważ trudniej jest ustalić, która wartość jest powiązana z nazwą lub dlaczego ta wartość mogła się zmienić. Clojure ma zestaw narzędzi do radzenia sobie ze zmianami, o których dowiesz się w rozdziale 10. Kiedy uczysz się Clojure, przekonasz się, że rzadko trzeba zmieniać powiązanie nazwy / wartości. Oto jeden ze sposobów na napisanie poprzedniego kodu:

(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
źródło
Czy nie można łatwo zrobić tego samego w Ruby? Podana sugestia polega na zdefiniowaniu funkcji, która zwraca wartość. Ruby też ma funkcje!
Evan Zamir
Tak, wiem. Ale zamiast zachęcać do nadrzędnego sposobu rozwiązania proponowanego problemu (jak zmiana powiązań), Clojure przyjmuje paradygmat funkcjonalny.
Tiago Dall'Oca