Skip to content

Sometimes it's hard to be lazy

YS (being Clojure) is a functional language with immutable data structures and lazy evaluation.

Laziness is cool because you can do complicated operations on large (even infinite) sequences without having to load everything into memory.

But laziness can be very confusing when you're not used to it or you don't expect it.

Infinite Sequences🔗

Here are some functions that return infinite sequences:

seq1 =: range()    # 0, 1, 2, 3, ... to infinity
seq2 =: iterate((inc + inc) 0) # 0, 2, 4, 6, ... to infinity
seq3 =: repeat(0) # 0, 0, 0, 0, ... to infinity
seq4 =: cycle([1 2 3]) # 1, 2, 3, 1, 2, 3, ... to infinity

These sequences are all lazy.

Don't try to print them. They will never terminate.

But you can do things like square all the numbers in an infinite sequence:

$ ys -e 'map sqr: range()'
$

This worked fine and finished instantly because we never printed the result. Therefore the infinite sequence was never realized.

The secret to making this useful is to take parts of the sequence.

Let's print the first 10 numbers of the sequence:

$ ys -e 'say: range().map(sqr).take(10)'
(0 1 4 9 16 25 36 49 64 81)

Or let's get the 2025th number in the sequence:

$ ys -e 'range().map(sqr).take(2025).last()'
4096576

or:

$ ys -e 'range().map(sqr).drop(2024).first()'
4096576

The for loop gotcha🔗

The for function returns a lazy sequence.

$ ys -e '
say:
  for i (1 .. 10):
    i:sqr
'
(1 4 9 16 25 36 49 64 81 100)

That works but say I want to print 1 per line.

$ ys -e '
for i (1 .. 10):
  say: i:sqr
'

Nothing happens.

That's because we used for to create a lazy sequence but never did anything with it.

Therefore the sequence is never realized, and therefore nothing is printed.

Clojure has a doall functions that forces things within it to be realized.

$ ys -e '
doall:
  for i (1 .. 10):
    say: i:sqr
'
1
4
9
16
25
36
49
64
81
100

Nice! That's what we wanted.

But do we have to add doall all the time?

YS has an each function that acts like a for inside a doall block.

$ ys -e '
each i (1 .. 10):
  say: i:sqr
'
1
4
9
16
25
36
49
64
81
100

Clojure (and thus YS) also has a doseq that acts like each.

Also the common Clojure map and filter functions return lazy sequences, and sometimes cause you problems.

Try the mapv and filterv functions instead. They return vectors instead of lazy sequences. Vectors are not lazy, so they will be realized immediately.

Comments