For the complete documentation index, see llms.txt. This page is also available as Markdown.

Ranges

Lazy, typed, iterable intervals with exclusive boundaries and custom stepping

BoxLang Ranges are first-class interval objects supporting lazy iteration, multiple element types, exclusive boundaries, custom stepping, and the IRangeable interface for custom types. Ranges are not arrays β€” they're lightweight objects that generate values on demand.

πŸš€ The Basics

Range as a First-Class Object

Ranges are created with the .. operator and produce a Range object:

myRange = 1..5
// Range object β€” NOT immediately an array

Ranges are iterable and seamlessly coerce to arrays when needed:

result = arrayToList( 1..5, "," )  // "1,2,3,4,5"
result = arrayLen( 1..10 )         // 10

But they're not arrays β€” they're lightweight objects that generate values on demand. This means huge and even infinite ranges are cheap to create and use.

πŸ”’ Supported Types

Integers and Decimals

Integer and decimal ranges work naturally with automatic direction detection:

1..5             // 1, 2, 3, 4, 5
3.5..1.5         // 3.5, 2.5, 1.5 (descending auto-detected)
(0..1).step(0.25) // 0, 0.25, 0.50, 0.75, 1.00

Characters

Single-character strings produce character ranges:

DateTime

DateTime ranges iterate by day by default and support unit-based stepping:

Any Comparable Type (Contains-Only)

Any two values of the same type that are naturally comparable can form a range β€” even if there's no way to iterate between them. Multi-character strings are a good example: they have a well-defined lexicographic ordering, so contains() works, but there's no natural "next" value after "foo":

This works with any Java class that implements Comparable β€” including JDK types:

Attempting to iterate a non-iterable range throws an error:

This pattern works for any type with a natural ordering β€” use it as a bounds check without needing to enumerate values.

Custom Types via IRangeable

Any BoxLang or Java class can participate in ranges by implementing the ortus.boxlang.runtime.types.IRangeable interface. See Custom Rangeable Types below for complete details.

🌐 Unbounded and Half-Bounded Ranges

Ranges can be open on one or both ends:

Half-bounded ranges are lazy β€” you can iterate them with a break condition:

Open-start and fully-open ranges cannot be iterated (no starting point):

But they still support contains():

Typed Unbounded Ranges

A fully unbounded range (..) contains everything non-null by default. Use .type() to constrain it to a specific type β€” leveraging BoxLang's loose casting system:

Any BoxLang type name works β€” string, numeric, integer, boolean, date, array, struct, etc. For custom classes, the instanceof operator is used:

For exact Java class matching with no coercion, pass a Class reference directly:

πŸ”€ Exclusive Boundaries

Four boundary modes via operators:

These affect both iteration and contains():

πŸͺœ Custom Stepping

The step() method returns a new range (copy-on-write) with a custom step:

Unit-based stepping for DateTime and custom IRangeable types:

🌊 Lazy Iteration and Streaming

Ranges are never materialized into memory unless you ask. This means huge and even infinite ranges are cheap:

Full Java Stream API integration:

Use map, filter, takeWhile, anyMatch, limit, and all other stream operations.

πŸ” Contains Semantics

Simple ranges (step = 1, no unit): Bounds Check

Stepped ranges (step > 1 or unit): Step-Reachability Check

When a range has a custom step, contains() verifies the value is actually reachable by the stepper β€” not just within bounds:

This follows the Python/Kotlin convention where a stepped range represents a discrete set.

For half-bounded stepped ranges, the iteration stops once the target is exceeded β€” so this is safe and terminates:

Non-iterable ranges: Always Bounds Check

Ranges that cannot be iterated (no start value, or no stepper) always fall back to a simple bounds check:

Range-in-Range Contains

You can check if an entire range fits within another:

πŸ—œοΈ Clamping Values

The clamp() method snaps a value to the closest boundary if it falls outside the range:

Half-bounded ranges snap to the one bound that exists:

Fully unbounded ranges just type-check and return the value:

πŸ“ Position Checks

Check whether a value falls before or after a range without getting a full contains() answer:

Exclusive boundaries are respected:

Incompatible types return false (same as contains()):

πŸ“‹ Member Methods

Query Methods

Method
Description

contains(value)

Check if value (or inner range) is within this range

isValueBefore(value)

True if value is before the low boundary

isValueAfter(value)

True if value is after the high boundary

isEmpty()

True if iteration would produce zero elements

isAscending()

True if step is positive

isIterable()

True if the range can be iterated

isBounded()

True if both start and end are present

isUnbounded()

True if both start and end are absent

isHalfBounded()

True if exactly one bound is present

hasFrom() / hasTo()

Check individual bounds

Accessors

Method
Description

getFrom() / getTo()

Get bound values

getStep()

Get current step value

Transformation Methods

Method
Description

clamp(value)

Snap value to the closest boundary if out of bounds

type(typeName)

New range constrained to a BoxLang type (uses casters for coercion)

type(class)

New range constrained to an exact Java class (strict instanceof)

step(n)

New range with numeric step

step(n, unit)

New range with unit-based step

asc()

Force ascending (empty if already descending)

desc()

Force descending (empty if already ascending)

Conversion Methods

Method
Description

toArray()

Materialize to array (requires bounded + iterable)

stream()

Get a Java Stream for functional pipelines

toString()

Human-readable representation

equals(other)

Structural equality (bounds, step, exclusivity)

βœ… Empty Ranges and Truthiness

Ranges are truthy if non-empty, falsy if empty:

πŸ”’ Operator Precedence

Arithmetic binds tighter than the range operator:

You can use any expression as operands:

πŸ“ Copy-on-Write Semantics

All modifier methods return new Range instances β€” the original is never mutated:

⚠️ Error Cases

These throw runtime errors:

🧩 Custom Rangeable Types: IRangeable

Any Java or BoxLang class can become rangeable by implementing ortus.boxlang.runtime.types.IRangeable. You need to provide:

  • rangeAdvance( step ) β€” Return a new instance advanced by step positions

  • rangeCompare( other ) β€” Compare to another instance (negative = less, 0 = equal, positive = greater)

  • rangeCoerce( val ) β€” Convert arbitrary values to your type for contains() checks

  • rangeStepFromUnit( amount, unit ) β€” (Optional) Convert a unit-based step to a numeric step

  • rangeUnitStepper( unit ) β€” (Optional) Return a custom stepper closure for non-uniform stepping

Example 1: Fibonacci β€” Infinite Non-Linear Sequences

Not all sequences advance linearly. The Fibonacci sequence is a perfect example β€” each value depends on the previous two, so rangeAdvance(1) produces values that grow exponentially. BoxLang's range system handles this naturally.

Usage:

Example 2: Roman Numerals

A Roman numeral class that stores an integer internally and converts to/from Roman notation. This demonstrates iteration, stepping, and coercion-based contains.

Usage:

Example 3: Musical Notes with Unit Stepping

This is where it gets interesting. A Musical Note class that supports chromatic, whole-step, major-third, octave, major scale, and minor scale stepping β€” plus stream composition for infinite sequences.

Usage:

Implementation Checklist

  1. rangeAdvance( step ) β€” Return a new instance moved by step positions. This is your "next value" generator.

  2. rangeCompare( other ) β€” Return negative/zero/positive like Java's compareTo(). This defines ordering for contains checks and iteration bounds.

  3. rangeCoerce( val ) β€” Convert foreign values to your type. Return null if the value can't be converted. This enables contains() to accept multiple input types.

  4. rangeStepFromUnit( amount, unit ) (optional) β€” Convert named units to numeric step values. Only needed if your type supports unit-based stepping with uniform intervals.

  5. rangeUnitStepper( unit ) (optional) β€” Return a closure (current, amount) => nextValue for non-uniform stepping patterns. Return null to fall through to rangeStepFromUnit().

πŸ“š Additional Resources

Available in BoxLang 1.14.0+

Last updated

Was this helpful?