Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using doseq/for with infinite sequences

in Clojure, one can iterate over a sequence by the help of the function for or similarly with doseq for side-effects and getting nil as return value:

(doseq [x (range 3)]
    (prn x))

; 0
; 1
; 2

for the case the sequence is infinite, there is a way to introduce a break condition:

(doseq [x (range) :while (< x 3)]
    (prn x))

This would produce the same output as above.

As a specialty there is a very interesting behavior when you use more than one sequence. As the documentation calls it: "Collections are iterated in a nested fashion, rightmost fastest".

(doseq [x (range 3) y (range 3)]
    (prn x y))

; 0 0
; 0 1
; 0 2
; 1 0
; 1 1
; 1 2
; 2 0
; 2 1
; 2 2

What happens however, if the sequences are infinite again. When the last one is infinite, it works quite well. this would work equally as the example before:

(doseq [x (range 3) y (range) :while (< y 3)]
    (prn x y))

If the first one is infinite, the resulting output is as expected, but for some reasons the loop does not stop after the last line is printed. In other words: the repl keeps working.

(doseq [x (range) y (range 3) :while (< x 3)]
    (prn x y))

Can anybody explain this behavior?

like image 353
Anton Harald Avatar asked Mar 08 '16 15:03

Anton Harald


2 Answers

This doesn't make sense:

(doseq [x (range) 
        y (range 3) 
        :while (< x 3)]
  (prn x y))

It should be:

(doseq [x (range) 
        :while (< x 3)
        y (range 3)]
  (prn x y))

...which terminates.

Think of it this way:

(doseq [x (range)]
  (doseq [y (range 3) 
          :while (< x 3)]
    (prn x y)))

vs:

(doseq [x (range)
        :while (< x 3)]
  (doseq [y (range 3)]
    (prn x y)))

In the original version, outer loop is infinite, having that :while in the inner loops doesn't make a difference. The loop continues on, it just does nothing. In the fixed version :while terminates the outer loop.

like image 152
muhuk Avatar answered Oct 27 '22 13:10

muhuk


Given muhuk's excellent explanation, it seems like a good argument for being explicit and not over-compressing the code. So maybe like this:

(doseq [x (range 3)]
  (doseq [y (range 3)]
    (prn x y)))

if you can control the input sequence, or like this:

(let [x-vals    (range)
      y-vals    (range) ]
  (doseq [x x-vals :while (< x 3)]
    (doseq [y y-vals :while (< y 3)]
      (prn x y))))

if the input sequence (eg x-vals or y-vals) is provided to you from the caller.

like image 40
Alan Thompson Avatar answered Oct 27 '22 11:10

Alan Thompson