2012-01-11 31 views
12

Uczę Clojure i zaskoczony brzmienie:Zaintrygowany: Clojure dla pętli z: while -> nieoczekiwane zachowanie?

user=> (for [a (range 1 4) b (range 1 4)] [a b]) 
([1 1] [1 2] [1 3] [2 1] [2 2] [2 3] [3 1] [3 2] [3 3]); _no surprise here_ 

Dodajmy :while (not= a b), spodziewam się, aby zobaczyć pustą listę jako pętla powinna zatrzymać, jeśli warunek jest fałszywy. W tym przypadku jest to pierwszy element, w którym a = b = 1. Zobaczmy:

user=> (for [a (range 1 4) b (range 1 4) :while (not= a b) ] [a b]) 

([2 1] [3 1] [3 2]) ; _surprise!_ 

Zmiana :while do :when odfiltrować (= a b) par

user=> (for [a (range 1 4) b (range 1 4) :when (not= a b) ] [a b]) 
([1 2] [1 3] [2 1] [2 3] [3 1] [3 2]); _expected_ 

Czy ktoś może wyjaśnić dlaczego (for [ ... :while ..] ...) zachowuje się jak ten?

Używam Clojure 1.3 na OS X.

Dziękuję i przepraszam za brak formatowania. To jest mój dziewiczy post na StackOverflow.

Odpowiedz

9

Spójrzmy na każdą iterację.

a = 1 
    b = 1 -> a == b, break because of while 

a = 2 
    b = 1 -> a != b, print [2 1] 
    b = 2 -> a == b, break because of while 

a = 3 
    b = 1 -> a != b, print [3 1] 
    b = 2 -> a != b, print [3 2] 
    b = 3 -> a == b, break because of while 
+0

Dziękuję, Nikita. Więc jeśli: dotyczy tylko wewnętrznej pętli, to jak mogę ją zastosować do zewnętrznej pętli? Problem, który mam, to: (od [od [: a: b: c: d: e: f] do [: a: b: c: d: e: f]: let [ścieżka (find-path ab)] : while path) path); pętla powinna się zatrzymać, gdy ścieżka jest zerowa. – jbear

+0

Przepraszam, miałem na myśli (dla [od [: a: b: c: d: e: f] do [: a: b: c: d: e: f]: let [ścieżka (find-ścieżka od do) ]: while path] path) – jbear

+0

@jbear, nie wiem :(Może lepiej będzie utworzyć leniwy seq ścieżek i weź gdy nie są one puste.Nie jestem pewien, że 'for' jest leniwy. –

3

Warunkiem w for:while kończy tylko najbardziej wewnętrznych pętli. Cały czas używam for, ale :while tak rzadko, że nigdy tego nie zauważyłem; dzięki za świetne pytanie!

Niestety uważam, że najlepszą rzeczą, jaką można zrobić, jest owinięcie take-while wokół for, ponieważ chcemy mieć "globalny" licznik zatrzymań na sekwencji wyjściowej, a nie licznik zatrzymań na jednej z sekwencji wejściowych, które iterujemy koniec. Na przykład:

(->> (for [a (range 1 4) 
      b (range 1 4)] 
     [a b]) 
    (take-while (fn [[a b]] (not= a b)))) 

() 
+1

Dla jasności, test': while' stosuje się do sekwencji bezpośrednio poprzedzającej, niekoniecznie do najbardziej wewnętrznej pętli –

+0

Dziękuję za wyjaśnienie, Alex, przyznając znaczenie tego języka, Nie mogłem się oprzeć wrażeniu, że jestem trochę rozczarowany, że jeden z fundamentalnych elementów - _list comprehension_ - powinien być raczej ograniczony.Mam nadzieję, że wraz z postępem mojej nauki odkryję więcej/lepsze sposoby robienia tego, z wielką pomocą od wszystkich – jbear

+0

@jbear W rzeczywistości jest to mniej ograniczone niż to być w inny sposób. W ten sposób możesz łatwo zawrzeć wyrażenie 'for' w' take-while', jeśli chcesz zachować zachowanie "globalne". Ale jeśli zachowanie "globalne" było tym, co "dla /: while", jak byś odzyskał obecne "pośrednie" zachowanie? Musiałbyś całkowicie zrezygnować z 'for' i zbudować gniazdo mapy szczura, mapcat, filter, take-while. – amalloy