2009-06-02 14 views
35

OK. Dotykałem Clojure i ciągle napotykam na ten sam problem. Weźmy ten mały fragment kodu:Przedefiniowanie zmiennej let'd w pętli Clojure

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

Teraz spodziewam się tego, aby wydrukować sekwencję zaczynając od 128, jak tak:

128 
64 
32 
16 
8 
4 
2 

Zamiast tego, jest to pętla nieskończona, drukowanie 128 kółko. Oczywiście mój zamierzony efekt uboczny nie działa.

Więc jak mam przedefiniować wartość x w takiej pętli? Zdaję sobie sprawę, że to może nie być Lisp (mogłabym użyć anonimowej funkcji, która prawdopodobnie powtarza się sama), ale jeśli nie wymyślę jak ustawić taką zmienną, oszaleję.

Moim drugim domysłem byłoby użyć zestawu !, ale to daje "Nieprawidłowy cel przydziału", ponieważ nie jestem w formie wiążącej.

Proszę, oświeć mnie, jak to działa.

Odpowiedz

48

def definiuje warunek var, nawet jeśli używa się go w funkcji lub wewnętrznej pętli jakiegoś kodu. To, co otrzymasz w let, nie jest vars. Na the documentation for let:

Mieszkańcy utworzeni za pomocą let nie są zmiennymi. Raz utworzone wartości nigdy się nie zmieniają!

(Podkreślenie nie moje). Nie potrzebujesz tutaj stanu zmiennego, aby tu podać przykład; możesz użyć loop i recur.

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

Jeśli chcesz być wyobraźnia można uniknąć wyraźnego loop całkowicie.

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

Jeśli naprawdę chciał wykorzystać stan zmienny, atom może działać.

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

(Nie potrzebujemy do; while owija swoje ciało w wyraźnej jednego dla ciebie.) Jeśli naprawdę chciał to zrobić z vars trzeba by zrobić coś okropnego jak to.

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

Ale to jest bardzo brzydkie i wcale nie jest idiomatyczne Clojure. Aby skutecznie używać Clojure, powinieneś przestać myśleć w kategoriach zmiennego stanu. Z pewnością doprowadzi cię to do szału, próbując napisać kod Clojure w niefunkcjonalnym stylu. Po pewnym czasie może okazać się przyjemnym zaskoczeniem, jak rzadko potrzebujesz zmiennych zmiennych.

+1

Dzięki. Zdaję sobie sprawę, że moim sposobem nie była Lispy, ponieważ efekty uboczne są marszczone. Hackowałem coś (problem z projektem Eulera) i nie mogłem wykonać tego prostego testu, żeby udowodnić, że czegoś nie rozumiem. Dzięki za pomoc. Zapomniałem, że pętla może się powtarzać, która działa bardzo czysto (bez dodatkowej funkcji rekursji). – MBCook

+4

Efekty uboczne to Lispy w zależności od tego, na który Lisp patrzysz. W Common Lisp uciekłbyś (pętla dla x = 128 wtedy (/ x 2) podczas gdy (> x 1) do (druk x)). Ale efekty uboczne nie są Clojurish. –

+1

To bardzo stary Ale to jest bardzo dobra odpowiedź, jestem nowy w Clojure, to uratowało mnie od wielu godzin zmagań z tym samym problemem. Dziękuję bardzo @BrianCarper – shan

12

Vars (to, co dostajesz, kiedy "def" coś) nie są przeznaczone do przydzielenia (ale może być):

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

Nie ma nic Cię powstrzymuje od robienia:

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

Jeśli chcesz ustawialny „miejsce” gwint lokalnego można użyć „wiązania” i „zestaw”:

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 

Więc można wr ite pętla jak ta:

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

Ale myślę, że to jest dość jednoznaczne.

5

Jeśli uważasz, że posiadające zmienne zmienne lokalne w czystych funkcjach byłoby miłą wygodną funkcją, która nie wyrządzi szkody, ponieważ funkcja nadal pozostaje czysta, możesz być zainteresowany dyskusją na liście dyskusyjnej, w której Rich Hickey wyjaśnia przyczyny usunięcia je z języka. Why not mutable locals?

odpowiedniej części:

Jeśli miejscowi były zmienne, czyli zmienny, a następnie zamknięcia może zamknąć nad stan zmienny, a biorąc pod uwagę, że zamknięcia może uciec (bez jakiegoś dodatkowego zakazu samo), wynik byłby niebezpieczny dla wątków. A ludzie z pewnością zrobiliby to, np. pseudoobiekty oparte na zamknięciu. Wynik byłby ogromną dziurą w podejściu Clojure'a.

Bez zmiennego locals, ludzie są zmuszeni używać recur, funkcjonalnego konstruktora pętli. Chociaż na pierwszy rzut oka może się to wydawać dziwne, to jest ono równie zwięzłe jak pętle z mutacją, a powstałe w ten sposób wzorce mogą być ponownie użyte w innym miejscu w Clojure, tj. Powtarzać, zmniejszać, zmieniać, zamieniać itd. są wszystkie (logicznie) bardzo podobne. Mimo że udało mi się wykryć i zapobiec zmutowaniu zamknięć, postanowiłem zachować je w ten sposób, aby uzyskać spójność. Nawet w najmniejszym kontekście pętle niezmutujące są łatwiejsze do zrozumienia i debugowania niż mutujące. W każdym przypadku Vars są dostępne do użycia w razie potrzeby.

Większość obaw kolejnych stanowisk realizujących with-local-vars makro;)

1

Można użyć więcej idiomatically iterate i take-while zamiast

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

(128 64 32 16 8 4 2) 
user> 
Powiązane problemy