range
stdlib/core/range.hk: Range, half-open [lo, hi) integer iteration.
It fills the no-while, no-for, no-ranges gap with stdlib and no syntax: a counting loop is Range.new(0, n).each!(|i| ...) in place of a hand-rolled var/loop/if/break. The combinator surface (each!/map/fold) mirrors List, so a range drops into the same pipelines. int.upto (inclusive) and int.times! (see int.hk) are the ergonomic entry points built on top.
Bounds are int, the default numeric tier, and never a fixed-width type.
Range
opaque Range
lo: int
hi: int
end
A half-open integer range [lo, hi): iteration covers lo, lo+1, … hi-1. Empty when hi <= lo. Construct with Range.new or the inclusive int.upto.
impl Range
new
def new(lo: int, hi: int) -> Range
The half-open range [lo, hi).
Range.new(0, 3).length => 3
Range.new(3, 3).length => 0
length
prop length(self) -> int
The element count: hi - lo, clamped at 0 for an empty range.
Range.new(2, 7).length => 5
Range.new(7, 2).length => 0
each!
def each!(self, f: (int) -> () [e]) -> () [e]
Applies the effectful f to each element in order, for its effects. A counting loop and no recursion, and a large range adds no call-stack depth. @no-doctest: effectful iteration returning unit, with no value to assert
map
def map(self, f: (int) -> U) -> List<U>
The list of f applied to each element, in order. Mirrors List.map. A counting loop like each!, and a large range adds no call-stack depth.
Range.new(0, 4).map(|i| i * i).length => 4
fold
def fold(self, init: U, f: (U, int) -> U) -> U
The left fold of f over the elements: f(…f(f(init, lo), lo+1)…). An empty range folds to init. Mirrors List.fold. A counting loop like each!, and a large range adds no call-stack depth.
Range.new(0, 5).fold(0, |acc, i| acc + i) => 10
Range.new(0, 0).fold(99, |acc, i| acc + i) => 99
to_list
def to_list(self) -> List<int>
The elements as a List<int>.
Range.new(2, 5).to_list().length => 3
impl Eq<Range>
eq?
def eq?(self, other: Self) -> bool
Equal when both bounds are equal. Two empty ranges with different bounds are unequal.
Range.new(0, 3) == Range.new(0, 3) => true
Range.new(3, 3) == Range.new(5, 5) => false
impl Hash<Range>
hash
prop hash(self) -> u64
Combines the hashes of the two bounds.
Range.new(0, 3).hash == Range.new(0, 3).hash => true
impl Display<Range>
to_string
def to_string(self) -> string
Renders the half-open interval as [lo, hi).
Range.new(0, 3).to_string() => "[0, 3)"
impl Encode<Range>
encode!
def encode!<S: Serializer>(self, s: S) -> ()
Writes the bounds as a two-field struct, lo then hi.
got: Result<Range, deserializer.DecodeError> = codec.from_bytes(codec.to_bytes(Range.new(2, 9)))
got.map(|r| r == Range.new(2, 9)).unwrap_or(false) => true
impl Decode<Range>
decode!
def decode!<D: Deserializer>(d: D, depth: int) -> Result<Range, DecodeError>
Reads the two bounds Encode writes.
@no-doctest: see the trait method in core/decode; the Encode example round-trips through it