Nếu bạn có thể sử dụng def để xác định lại các biến thì làm thế nào được coi là bất biến?


10

Cố gắng học Clojure và bạn không thể không được nói liên tục về cách Clojure nói về dữ liệu bất biến. Nhưng bạn có thể dễ dàng xác định lại một biến bằng cách sử dụng defphải không? Tôi hiểu rằng các nhà phát triển Clojure tránh điều này nhưng bạn có thể tránh thay đổi các biến trong bất kỳ ngôn ngữ nào giống nhau. Ai đó có thể giải thích cho tôi làm thế nào điều này là khác nhau, bởi vì tôi nghĩ rằng tôi đang thiếu điều đó từ các hướng dẫn và sách tôi đang đọc.

Để cho một ví dụ như thế nào là

a = 1
a = 2

trong Ruby (hoặc blub, nếu bạn thích) khác với

(def a 1)
(def a 2)

ở Clojure?

Câu trả lời:


9

Như bạn đã nhận thấy, thực tế là tính đột biến không được khuyến khích trong Clojure không có nghĩa là nó bị cấm và không có cấu trúc nào hỗ trợ nó. Vì vậy, bạn đã đúng khi sử dụng defbạn có thể thay đổi / biến đổi một ràng buộc trong môi trường theo cách tương tự như việc gán trong các ngôn ngữ khác (xem tài liệu Clojure trên vars ). Bằng cách thay đổi các ràng buộc trong môi trường toàn cầu, bạn cũng thay đổi các đối tượng dữ liệu sử dụng các liên kết này. Ví dụ:

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

Lưu ý rằng sau khi xác định lại ràng buộc cho x, chức năng fcũng đã thay đổi, bởi vì cơ thể của nó sử dụng liên kết đó.

So sánh điều này với các ngôn ngữ trong đó xác định lại một biến không xóa ràng buộc cũ mà chỉ làm mờ nó, tức là nó làm cho nó vô hình trong phạm vi xuất hiện sau định nghĩa mới. Xem điều gì xảy ra nếu bạn viết cùng một mã trong 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

Lưu ý rằng sau định nghĩa thứ hai x, hàm fvẫn sử dụng liên kết x = 1trong phạm vi khi được định nghĩa, nghĩa là liên kết val x = 100không ghi đè lên liên kết trước đó val x = 1.

Tóm lại: Clojure cho phép thay đổi môi trường toàn cầu và xác định lại các ràng buộc trong đó. Có thể tránh điều này, như các ngôn ngữ khác như SML làm, nhưng defcấu trúc trong Clojure có nghĩa là truy cập và làm thay đổi môi trường toàn cầu. Trong thực tế, điều này rất giống với những gì bài tập có thể làm trong các ngôn ngữ bắt buộc như Java, C ++, Python.

Tuy nhiên, Clojure cung cấp rất nhiều cấu trúc và thư viện để tránh đột biến và bạn có thể đi một chặng đường dài mà không cần sử dụng nó. Tránh đột biến là phong cách lập trình ưa thích trong Clojure.


1
Tránh đột biến là phong cách lập trình ưa thích. Tôi đề nghị rằng tuyên bố này áp dụng cho mọi ngôn ngữ ngày nay; không chỉ Clojure;)
David Arno

2

Clojure là tất cả về dữ liệu bất biến

Clojure là về việc quản lý trạng thái đột biến bằng cách kiểm soát các điểm đột biến (ví dụ: Refs, Atoms, Agents và Vars). Mặc dù, tất nhiên, bất kỳ mã Java nào bạn sử dụng thông qua interop đều có thể làm như vậy.

Nhưng bạn có thể dễ dàng xác định lại một biến bằng cách sử dụng def phải không?

Nếu bạn có nghĩa là liên kết một Var(trái ngược với, ví dụ, một biến cục bộ) với một giá trị khác, thì có. Trên thực tế, như đã lưu ý trong Vars và Môi trường toàn cầu , Varchúng được đặc biệt bao gồm là một trong bốn "loại tham chiếu" của Clojure (mặc dù tôi nói rằng chúng chủ yếu đề cập đến các động lực Var ở đó).

Với Lisps, có một lịch sử lâu dài về việc thực hiện các hoạt động lập trình tương tác, khám phá thông qua REPL. Điều này thường liên quan đến việc xác định các biến và hàm mới, cũng như xác định lại các biến cũ. Tuy nhiên, bên ngoài REPL, defmột Varhình thức được coi là kém.


1

Từ Clojure cho sự dũng cảm và sự thật

Ví dụ: trong Ruby, bạn có thể thực hiện nhiều phép gán cho một biến để xây dựng giá trị của nó:

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

Bạn có thể muốn làm điều gì đó tương tự trong 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!")))

Tuy nhiên, việc thay đổi giá trị được liên kết với một tên như thế này có thể khiến việc hiểu hành vi chương trình của bạn trở nên khó khăn hơn vì khó biết giá trị nào được liên kết với tên hoặc tại sao giá trị đó có thể thay đổi. Clojure có một bộ công cụ để xử lý sự thay đổi, bạn sẽ tìm hiểu về Chương 10. Khi bạn học Clojure, bạn sẽ thấy rằng bạn sẽ hiếm khi cần thay đổi liên kết tên / giá trị. Đây là một cách bạn có thể viết mã trước đó:

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

Không ai có thể dễ dàng làm điều tương tự trong Ruby? Gợi ý đưa ra chỉ đơn giản là xác định hàm trả về giá trị. Ruby cũng có chức năng!
Evan Zamir

Vâng tôi biết. Nhưng thay vì khuyến khích một cách bắt buộc để giải quyết vấn đề được đề xuất (như thay đổi các ràng buộc), Clojure áp dụng mô hình funcional.
Tiago Dall'Oca
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.