Iterate vs (def x (iterate ...)) in the clj repl

When I type user=> (iterate inc 1) in the repl (clj), it never returns, whereas
user=> (def x (iterate inc 1))
returns immediately. Why is that ?

Its because the result of iterate is an infinite sequence, and the REPL is trying to print the entire sequence, but because it is infinite, it never ends, and thus never returns. The def doesn’t do that, it just creates a variable pointing to the infinite sequence. It is the print in Read Eval Print Loop which causes it to hang.

I’ll explain.

The function iterate returns a sequence. A sequence is a collection which can be infinite in length. That’s because you can continuously call next on the sequence and it will keep returning more and more values without ever running out.

In your case, (iterate inc 1) will just keep returning the previous number + 1 over and over, forever, as next is called on it.

Now when you just type: user=> (iterate inc 1) you ask the REPL to loop over the sequence, accumulate the whole result and once done to print it all. But since it is an infinite sequence, it never ends, so the REPL never returns, and the elements are accumulated forever.

When you put the sequence in a variable using def, you are not asking its elements to be read, so you are only moving the sequence around. That doesn’t cause an infinite read of the sequence. In fact nothing is read from the sequence.

2 Likes

Expanding on @didibus’ answer with an example:

Clojure 1.10.1
user=> (def natural-numbers (iterate inc 0))
#'user/natural-numbers
user=> (take 5 natural-numbers )
(0 1 2 3 4)
user=> (take 50 natural-numbers )
(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49)
user=> natural-numbers 
(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
;; Abort with C-c

The last statement won’t complete, because there’s an infinite amount of integers. Well, to be perfectly pedantic, we would get an exception when reaching long overflow at Long/MAX_VALUE => 9223372036854775807, but getting there would take a while.

Here’s a few nice things about infinite sequences:

  • They won’t eat up memory until you call them
  • clojure.core/lazy-seq lets you construct them by hand
  • map and filter returns them implicitly, so you can map and filter on an infinite sequence.

To realize a lazy sequence, you can use (doall my-lazy-seq) or (vec my-lazy-seq).

3 Likes

Haha, that’s true, here’s how to get around that limit using auto-promoting add:

(take 10 (iterate #(+' 1 %) Long/MAX_VALUE))
1 Like

You can also set *print-length* to limit how many items repl will print:

(set! *print-length* 10)
=> 10
(iterate inc 1)
=> (1 2 3 4 5 6 7 8 9 10 ...)
5 Likes

When you say read, are you referring to the “reader” ?

Oups, no sorry. By read I mean accessing an element.

The REPL needs to print the sequence to your screen, so it accesses each element in the sequence to be able to print them. Since there are infinite elements, it is never done accessing them.

When you do a def, there is no need for any element in the sequence to be accessed.

So what matters here is if the operation you perform on the sequence needs to access its elements. If it does, and it is not bounded, it will loop forever on them. That’s the case of the default print of the default REPL. But not the case for def.

Okay what I understand is that the REPL does eval on the form it receives as input and evaluating a infinite lazy seq actually realizes it ?

Hum… not quite but almost:

  1. You type (iterate inc 1)
  2. The REPL Reads what you type, so it parses the text you typed into code.
  3. The REPL Evaluates the parsed code, which creates an infinite lazy sequence. This does not hang, it succeeds very quickly and returns the lazy sequence.
  4. The REPL Prints the returned lazy sequence, this is when the REPL hangs, because to print all of it, it must get all of the elements inside the sequence, and since there are infinite elements, this never ends.
  5. The REPL Loops back to reading the next command you type, this never happens, because the Print from (4) never terminates.

When instead you type (def x (iterate inc 1)):

  1. The REPL Reads what you typed and parses it into code.
  2. The REPL Evaluates what you typed. So first it creates the lazy sequence, and it binds it to the x variable. It then returns the variable x (and not what x points too, which is the lazy sequence).
  3. It now Prints the returned variable x. This is not infinite, so it does so and returns.
  4. Now it Loops back waiting for your next input command.

TL;DR: Printing an infinite lazy sequence realizes it.

2 Likes