8.15.0.4

2.6 Iteration🔗ℹ

Many simple forms of iteration can be expressed through repetitions. For other cases Rhombus supports a proper implementation of tail-call handling (i.e., tail calls do not extend the continuation), so looping can be written as a recursive function. Still, a looping construct is convenient and useful for writing iterations that are not simple enough for repetitions but also do not need the full expressiveness of functions.

The for form supports iteration over sequences, which includes lists, arrays, maps, and sets. In the body of a for form, each each clause binds to an element of a sequence for each iteration. The length of the sequence determines the number of iterations. The .. operator creates a sequence of integers from a starting integer (inclusive) to an ending integer (exclusive):

> for:

    each i: 1..4

    println(i)

1

2

3

As a shorthand, an initial each form can be written using parentheses before the for body block:

> for (i: 1..4):

    println(i)

1

2

3

If a for body includes multiple each clauses, they are nested. That is, for each element of the first each clause, all elements are used for the second each clause, and so on.

> for:

    each friend: ["Alice", "Bob", "Carol"]

    each say: ["Hello", "Goodbye"]

    println(say +& ", " +& friend +& "!")

Hello, Alice!

Goodbye, Alice!

Hello, Bob!

Goodbye, Bob!

Hello, Carol!

Goodbye, Carol!

An advantage of having each clauses in the body of for, instead of putting them before the body as in many other languages, is that definitions or expressions can be written among each clauses.

> for:

    each friend: ["Alice", "Bob", "Carol"]

    let dear_friend = "dear " +& friend

    each say: ["Hello", "Goodbye"]

    println(say +& ", " +& dear_friend +& "!")

Hello, dear Alice!

Goodbye, dear Alice!

Hello, dear Bob!

Goodbye, dear Bob!

Hello, dear Carol!

Goodbye, dear Carol!

To draw elements from sequences in parallel, use a block of bindings immediately after each.

> for:

    each:

      friend: ["Alice", "Bob", "Carol"]

      index: 1..4

    println(index +& ". " +& friend)

1. Alice

2. Bob

3. Carol

Note that the shorthand form using parentheses for an initial each clause corresponds to this parallel mode, since the short is for a single each clause:

> for (friend: ["Alice", "Bob", "Carol"],

       index: 1..4):

    println(index +& ". " +& friend)

1. Alice

2. Bob

3. Carol

In this latest example, the sequence for index could be 1.. to avoid needing the length of the list for friend. When .. has no second argument, it creates an infinite sequence of integers, and when for iterates over sequences in parallel, it stops when the shortest sequence stops.

The for form acts as a comprehension form when a reducer is specified before the for body block. List serves as a reducer to generate a list, accumulating the values produced by each iteration of the for body.

> for List (i: 1..4):

    "number " +& i

["number 1", "number 2", "number 3"]

> for List:

    each i: [1, 2]

    each j: ["a", "b", "c"]

    [i, j]

[[1, "a"], [1, "b"], [1, "c"], [2, "a"], [2, "b"], [2, "c"]]

If you prefer, you can put the reducer at the end of a for body with ~into.

> for (i: 1..4):

    "number " +& i

    ~into List

["number 1", "number 2", "number 3"]

Map works as a reducer where the body of the for form must produce two values for each iteration: a key and a value.

> for Map (friend: ["alice", "bob", "carol"],

           index: 1..):

    values(index, friend)

{1: "alice", 2: "bob", 3: "carol"}

The values reducer implements the general case, where values is followed by a parenthesized sequence of identifiers with initial values, the for body can refer to those identifiers to get values from the previous iteration (or the initial values in the case of the first iteration), and the for body returns as many values as identifiers to provide new values for the identifiers.

> fun sum(ns :~ List):

    for values(sum = 0) (n: ns):

      sum+n

> sum([2, 3, 4])

9

In the same way that a List annotation specializes element access via [...], it also specializes how each within for iterates through a list. In the following example, nss is annotated as a list of lists, so both the outer and inner iterations are specialized—although that specialization is visible only as a change in performance, if at all.

> fun sum2d(nss :~ List.of(List.of(Number))):

    for values(sum = 0):

      each ns: nss

      each n: ns

      sum+n

> sum2d([[1], [2, 3, 4], [5, 6, 7], [8, 9]])

45