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.