2009-06-02 32 views
35

OK. Tôi đã được tinkering với Clojure và tôi liên tục chạy vào cùng một vấn đề. Chúng ta hãy xem đoạn này ít mã:Xác định lại biến số let'd trong vòng lặp Clojure

(let [x 128] 
    (while (> x 1) 
    (do 
     (println x) 
     (def x (/ x 2))))) 

Bây giờ tôi hy vọng điều này để in ra một chuỗi bắt đầu với 128 như vậy:

128 
64 
32 
16 
8 
4 
2 

Thay vào đó, nó là một vòng lặp vô hạn, in 128 hơn và hơn. Rõ ràng tác dụng phụ dự định của tôi không hoạt động.

Vậy làm cách nào để tôi xác định lại giá trị của x trong một vòng lặp như thế này? Tôi nhận ra điều này có thể không phải là Lisp (tôi có thể sử dụng một chức năng ẩn danh mà tự mình tái hiện, có lẽ), nhưng nếu tôi không tìm ra cách đặt biến như thế này, tôi sẽ phát điên.

Dự đoán khác của tôi là sử dụng tập hợp !, nhưng điều đó cho biết "Mục tiêu chuyển nhượng không hợp lệ", vì tôi không ở dạng ràng buộc.

Xin vui lòng, hãy khai sáng cho tôi về cách thức hoạt động này.

Trả lời

48

def định nghĩa một var toplevel, ngay cả khi bạn sử dụng nó trong một hàm hoặc vòng lặp bên trong của một số mã. Những gì bạn nhận được trong let không phải là vars. Per the documentation for let:

Người dân địa phương được tạo ra không phải là biến. Sau khi tạo ra các giá trị của họ không bao giờ thay đổi!

(Nhấn mạnh không phải của tôi). Bạn không cần trạng thái có thể thay đổi cho ví dụ của bạn tại đây; bạn có thể sử dụng looprecur.

(loop [x 128] 
    (when (> x 1) 
    (println x) 
    (recur (/ x 2)))) 

Nếu bạn muốn trở nên lạ mắt, bạn có thể tránh hoàn toàn rõ ràng loop hoàn toàn.

(let [xs (take-while #(> % 1) (iterate #(/ % 2) 128))] 
    (doseq [x xs] (println x))) 

Nếu bạn thực sự muốn sử dụng nhà nước có thể thay đổi, một atom có thể làm việc.

(let [x (atom 128)] 
    (while (> @x 1) 
    (println @x) 
    (swap! x #(/ %1 2)))) 

(Bạn không cần một do; while kết thúc tốt đẹp cơ thể của mình trong một một rõ ràng cho bạn.) Nếu bạn thực sự, thực sự muốn làm điều này với vars bạn phải làm điều gì đó khủng khiếp như điều này.

(with-local-vars [x 128] 
    (while (> (var-get x) 1) 
    (println (var-get x)) 
    (var-set x (/ (var-get x) 2)))) 

Nhưng điều này rất xấu và nó không phải là Clojure thành ngữ chút nào. Để sử dụng Clojure hiệu quả, bạn nên cố gắng dừng suy nghĩ về trạng thái có thể thay đổi. Nó chắc chắn sẽ khiến bạn điên rồ cố gắng viết mã Clojure theo một phong cách phi chức năng. Sau một thời gian bạn có thể tìm thấy nó là một ngạc nhiên thú vị như thế nào hiếm khi bạn thực sự cần biến có thể thay đổi.

+1

Cảm ơn. Tôi nhận ra con đường của tôi không phải là Lispy, vì các tác dụng phụ bị cau mày. Tôi đã hack qua một cái gì đó (một vấn đề dự án Euler) và không thể có được trường hợp thử nghiệm đơn giản để làm việc, chứng minh tôi đã không hiểu một cái gì đó. Cảm ơn đã giúp đỡ. Tôi quên vòng lặp có thể giữ recur, mà hoạt động rất sạch (không có chức năng bổ sung làm đệ quy). – MBCook

+4

Tác dụng phụ là Lispy tùy thuộc vào Lisp bạn đang xem. Trong Common Lisp, bạn sẽ tránh được (vòng lặp x = 128 rồi (/ x 2) trong khi (> x 1) làm (in x)). Nhưng tác dụng phụ không phải là Clojurish. –

+1

Nó rất cũ Nhưng đây là một câu trả lời rất tốt, tôi mới để Clojure này đã cứu tôi từ giờ đấu tranh với cùng một vấn đề. Cảm ơn bạn rất nhiều @BrianCarper – shan

12

Vars (đó là những gì bạn nhận được khi bạn "def" một cái gì đó) không có nghĩa là để được bố trí (nhưng có thể):

user=> (def k 1) 
#'user/k 
user=> k 
1 

Có gì ngăn cản bạn là từ thực hiện:

user=> (def k 2) 
#'user/k 
user=> k 
2 

Nếu bạn muốn có một settable "nơi" thread-địa phương mà bạn có thể sử dụng "ràng buộc" và "thiết lập!":

user=> (def j) ; this var is still unbound (no value) 
#'user/j 
user=> j 
java.lang.IllegalStateException: Var user/j is unbound. (NO_SOURCE_FILE:0) 
user=> (binding [j 0] j) 
0 

Vì vậy, sau đó bạn có thể wr ite một vòng lặp như thế này:

user=> (binding [j 0] 
     (while (< j 10) 
      (println j) 
      (set! j (inc j)))) 
0 
1 
2 
3 
4 
5 
6 
7 
8 
9 
nil 

Nhưng tôi nghĩ điều này là khá đơn giản.

5

Nếu bạn nghĩ rằng có các biến cục bộ có thể thay đổi trong các hàm thuần túy sẽ là một tính năng thuận tiện không gây hại, bởi vì hàm vẫn giữ nguyên, bạn có thể quan tâm đến cuộc thảo luận danh sách gửi thư này, nơi Rich Hickey giải thích lý do xóa chúng từ ngôn ngữ. Why not mutable locals?

phần liên quan:

Nếu người dân địa phương đã biến, tức là có thể thay đổi, sau đó đóng cửa có thể đóng trên nhà nước có thể thay đổi, và, cho rằng việc đóng cửa có thể thoát ra (nếu không có thêm cấm trên cùng), kết quả sẽ không an toàn. Và mọi người chắc chắn sẽ làm như vậy, ví dụ: các đối tượng giả dựa trên đóng cửa. Kết quả sẽ là một lỗ hổng lớn trong cách tiếp cận của Clojure.

Nếu không có người dân địa phương có thể thay đổi, mọi người buộc phải sử dụng lại, một cấu trúc vòng lặp chức năng. Mặc dù điều này có vẻ lạ lẫm lúc đầu, nó chỉ giống như ngắn gọn như các vòng lặp với đột biến và các mẫu kết quả có thể là được sử dụng lại ở Clojure, tức là recur, reduce, alter, commute vv là tất cả (lô-gic) rất giống nhau. Mặc dù tôi có thể phát hiện và ngăn chặn việc đóng cửa đột biến thoát khỏi, tôi quyết định giữ nó theo cách này để nhất quán. Ngay cả trong bối cảnh nhỏ nhất, các vòng lặp không đột biến là dễ hiểu và gỡ lỗi hơn so với các biến đổi đột biến. Trong mọi trường hợp, Vars có sẵn để sử dụng khi thích hợp.

Đa số những mối quan tâm bài viết tiếp theo thực hiện một with-local-vars vĩ mô;)

1

Bạn idiomatically hơn có thể sử dụng iteratetake-while thay vào đó,

user> (->> 128 
      (iterate #(/ % 2)) 
      (take-while (partial < 1))) 

(128 64 32 16 8 4 2) 
user> 
Các vấn đề liên quan