6. Ranges and For Loops
6.1 Range Expressions
-- Ranges use (start..end) syntax with inclusive bounds
let r = (1..5); -- Range from 1 to 5 inclusive
r println; -- (1..5)
r length println; -- 5
r toArray println; -- [1, 2, 3, 4, 5]
-- Stepped ranges: (start,next..end)
let evens = (0,2..10); -- step inferred as next - start = 2
evens toArray println; -- [0, 2, 4, 6, 8, 10]
-- Descending ranges infer step automatically
let down = (5..1); -- step inferred as -1
down toArray println; -- [5, 4, 3, 2, 1]
-- Descending with explicit step
let odds = (9,7..1); -- step = 7 - 9 = -2
odds toArray println; -- [9, 7, 5, 3, 1]
-- Infinite ranges
let nat = (1..); -- 1, 2, 3, 4, ... (no end)
let by3 = (0,3..); -- 0, 3, 6, 9, ... (stepped, no end)
6.2 For Loops over Ranges
-- Basic for loop: for (variable : iterable) { body }
for (i : (1..5)) {
i println;
}
-- Output: 1 2 3 4 5 (each on its own line)
-- Stepped range
for (i : (0,2..10)) {
i println;
}
-- Output: 0 2 4 6 8 10
-- Descending range (step inferred as -1)
for (i : (5..1)) {
i println;
}
-- Output: 5 4 3 2 1
-- Descending with explicit step
for (i : (10,7..1)) {
i println;
}
-- Output: 10 7 4 1
Range Optimization
When a range literal appears directly in a for loop (e.g., for (i : (1..100))), the compiler emits an inline counter loop. No Range object is allocated and no array is materialized.
6.3 For Loops over Arrays and Lists
-- For loop over an array
let arr = [10, 20, 30];
for (x : arr) {
x println;
}
-- Output: 10 20 30
-- For loop over a list
let lst = List(1, 2, 3);
for (x : lst) {
x println;
}
-- Output: 1 2 3
6.4 Range Built-in Functions
let r = (1..5);
-- length: number of elements in the range
r length println; -- 5
-- toArray: materialize into an [Int]
r toArray println; -- [1, 2, 3, 4, 5]
-- toList: create a lazy List<Int>
r toList println; -- List(1, 2, 3, 4, 5)
-- toList on an infinite range produces an infinite lazy list
let nat = (1..);
let nats = nat toList;
nats println; -- List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...)
6.5 Fraction Ranges
Ranges also support fraction endpoints and steps. Use fraction literals like 1/2 (no spaces around /).
-- Basic fraction range (step defaults to 1)
for (i : (1/2..5/2)) {
i println;
}
-- Output: 1/2 3/2 5/2
-- Stepped fraction range: (start,next..end)
for (i : (1/4, 1/2..2/1)) {
i println;
}
-- Output: 1/4 1/2 3/4 1/1 5/4 3/2 7/4 2/1
-- Descending fraction range (step inferred)
for (i : (3/1..1/2)) {
i println;
}
-- Output: 3/1 2/1 1/1
-- Fraction range as value
let fr = (1/4, 3/4..3/1);
fr length println; -- 6
fr toArray println; -- [1/4, 3/4, 5/4, 7/4, 9/4, 11/4]
fr toList println; -- List(1/4, 3/4, 5/4, 7/4, 9/4, 11/4)
-- Infinite fraction range
let flinf = (1/2..) toList;
flinf println; -- List(1/2, 3/2, 5/2, 7/2, 9/2, ...)
6.6 Indexing Ranges
Ranges are indexable with [], without materializing an array. Finite ranges index cyclically, just like arrays: the index wraps modulo the range's length, so negative indices count from the end. Infinite ranges have no length to wrap by; they use the absolute value of the index instead.
-- Finite ranges: cyclic indexing, like arrays
let r = (0..9);
r[3] println; -- 3
r[-1] println; -- 9 (last element)
r[13] println; -- 3 (wraps around)
-- Works with stepped and descending ranges
(0,2..10)[3] println; -- 6
(5..1)[1] println; -- 4
-- Infinite ranges: the absolute value of the index
let nat = (0..);
nat[5] println; -- 5
nat[-3] println; -- 3
-- Fraction ranges index the same way
(1/2..5/2)[1] println; -- 3/2
-- Indexing by a collection of indices, like arrays
(0..9)[[3, 1, 2]] println; -- [3, 1, 2]
-- Explicit @ auto-map over containers of ranges
[(0..9), (10..19)] @ [2] println; -- [2, 12]