Skip to content

Commit 95143fc

Browse files
committed
blog: Add 2025-07-30.md
1 parent 23a1db7 commit 95143fc

1 file changed

Lines changed: 153 additions & 0 deletions

File tree

blog/2025-07-30.md

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
---
2+
title: Sometimes it's hard to be lazy
3+
date: 2025-07-30
4+
draft: false
5+
authors: [ingydotnet]
6+
categories: [Summer-of-YS]
7+
edit: blog/2025-07-30.md
8+
comments: true
9+
---
10+
11+
YS (being Clojure) is a functional language with immutable data structures
12+
and lazy evaluation.
13+
14+
Laziness is cool because you can do complicated operations on large (even
15+
infinite) sequences without having to load everything into memory.
16+
17+
But laziness can be very confusing when you're not used to it or you don't
18+
expect it.
19+
20+
<!-- more -->
21+
22+
23+
## Infinite Sequences
24+
25+
Here are some functions that return infinite sequences:
26+
27+
```yaml
28+
seq1 =: range() # 0, 1, 2, 3, ... to infinity
29+
seq2 =: iterate((inc + inc) 0) # 0, 2, 4, 6, ... to infinity
30+
seq3 =: repeat(0) # 0, 0, 0, 0, ... to infinity
31+
seq4 =: cycle([1 2 3]) # 1, 2, 3, 1, 2, 3, ... to infinity
32+
```
33+
34+
These sequences are all lazy.
35+
36+
Don't try to print them.
37+
They will never terminate.
38+
39+
But you can do things like square all the numbers in an infinite sequence:
40+
41+
```bash
42+
$ ys -e 'map sqr: range()'
43+
$
44+
```
45+
46+
This worked fine and finished instantly because we never printed the result.
47+
Therefore the infinite sequence was never realized.
48+
49+
The secret to making this useful is to `take` parts of the sequence.
50+
51+
Let's print the first 10 numbers of the sequence:
52+
53+
```bash
54+
$ ys -e 'say: range().map(sqr).take(10)'
55+
(0 1 4 9 16 25 36 49 64 81)
56+
```
57+
58+
Or let's get the 2025th number in the sequence:
59+
60+
```bash
61+
$ ys -e 'range().map(sqr).take(2025).last()'
62+
4096576
63+
```
64+
65+
or:
66+
67+
```bash
68+
$ ys -e 'range().map(sqr).drop(2024).first()'
69+
4096576
70+
```
71+
72+
73+
## The `for` loop gotcha
74+
75+
The `for` function returns a lazy sequence.
76+
77+
```bash
78+
$ ys -e '
79+
say:
80+
for i (1 .. 10):
81+
i:sqr
82+
'
83+
(1 4 9 16 25 36 49 64 81 100)
84+
```
85+
86+
That works but say I want to print 1 per line.
87+
88+
```bash
89+
$ ys -e '
90+
for i (1 .. 10):
91+
say: i:sqr
92+
'
93+
```
94+
95+
Nothing happens.
96+
97+
That's because we used `for` to create a lazy sequence but never did anything
98+
with it.
99+
100+
Therefore the sequence is never realized, and therefore nothing is printed.
101+
102+
Clojure has a `doall` functions that forces things within it to be realized.
103+
104+
```bash
105+
$ ys -e '
106+
doall:
107+
for i (1 .. 10):
108+
say: i:sqr
109+
'
110+
1
111+
4
112+
9
113+
16
114+
25
115+
36
116+
49
117+
64
118+
81
119+
100
120+
```
121+
122+
Nice!
123+
That's what we wanted.
124+
125+
But do we have to add `doall` all the time?
126+
127+
YS has an `each` function that acts like a `for` inside a `doall` block.
128+
129+
```bash
130+
$ ys -e '
131+
each i (1 .. 10):
132+
say: i:sqr
133+
'
134+
1
135+
4
136+
9
137+
16
138+
25
139+
36
140+
49
141+
64
142+
81
143+
100
144+
```
145+
146+
Clojure (and thus YS) also has a `doseq` that acts like each.
147+
148+
Also the common Clojure `map` and `filter` functions return lazy sequences, and
149+
sometimes cause you problems.
150+
151+
Try the `mapv` and `filterv` functions instead.
152+
They return vectors instead of lazy sequences.
153+
Vectors are not lazy, so they will be realized immediately.

0 commit comments

Comments
 (0)