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.