Ranges feature prominently in Ki, as restricted numeric types, in loops, really just everywhere.

Range Types

range MonthOfYear [1..12]
range Positive [0..]
range Negative [-1.., -1]

A range is a restricted numeric type. It only requires one bound (note Positive and Negative above) and will also accept a third "step" argument that defaults to 1 if not given.

Range Literals

A range literal is a generator

fn main() {
    for n in [0..10] {
        if n % 2 == 0 {

A range literal defines a generator with a start, an end, and a step. End and step are both optional; without an end, the range will continue infinitely, and without a step, the range will increment by 1.

fn main() {
    val single_evens: [2..8, 2]
    val single_evens_backwards: [8..2, -2]
    val all_evens: [2.., 2]

These ranges are inclusive. If you want exclusive ranges:

fn main() {
    single_evens: (1..9, 2)
    single_evens_backwards: (9..1, -2)
    all_evens: (1.., 2)

You can also mix ( and ], for the common C-style loop:

fn main() {
    val fruits: array(["apple", "banana", "cherry"])

    for (i in [0..fruits.length)) {
        echo("Fruit ${i + 1}: fruits[i]")

Of course, you should use for-in here.

A Word Of Warning

It is very easy to use ( when you meant ] and vice-versa. We justified using them despite this pitfall because they're far away on the keyboard, and errors of logic are far less common than errors of typing. Further, they match the interval notation used in mathematics. That said, there are very few cases where numeric iteration is truly required. Use for-in whenever possible; moreover, if you find yourself using numeric iteration often, consider refactoring to use the safer and more idiomatic for-in.