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 def
bạ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 f
cũ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 f
vẫn sử dụng liên kết x = 1
trong phạm vi khi được định nghĩa, nghĩa là liên kết val x = 100
khô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 def
cấ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.