hanki

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