Loops and the Iteration Protocol

Monte has only two kinds of looping constructs: for loops, which consume iterators to process a series of elements, and while loops, which repeatedly consider a predicate before doing work. Both should be familiar to any experienced programmer; let’s explore them in greater detail.

for loops

A for loop is a simple structure that takes an iterable object and loops over it:

var x := 0
for i in (1..10):
    x += i

Here, we can clearly see the three elements of the for loop, the pattern, x; the iterable, 1..10, and the loop’s body, x += i. For each element in the iterable, the iterable is matched against the pattern, which is available within the body.

Within a for loop, the continue keyword will skip the current iteration of the loop, and break keyword will exit the loop altogether:

# Skip the even elements, and give up if we find multiples of three.
for i in (1..10):
    if (i % 2 == 0):
        continue
    if (i % 3 == 0):
        break
    x -= i

Pair Patterns

All iterables yield not just one element, but a pair of elements on every iteration. To access both elements at once, we can use a pair pattern:

def names := ["Scooby", "Shaggy", "Velma"]
for i => name in (names):
    traceln(`Name $i: $name`)

For a list, like in the previous example, the right-hand side of the pair matches the current element, and the left-hand side matches that element’s index. When iterating over a map, the pair will match the key and value:

def animals := [
    "Bagira"     => "panther",
    "Baloo"      => "bear",
    "Shere Khan" => "tiger",
]
for animal => species in (animals):
    traceln(`Animal $animal is a $species`)

while loops

In addition to the for loop, Monte provides a while loop:

var x := 1
while (x < 402):
    x *= 2

The while loop admits continue and break, just like in for loops.

Advanced Looping

The Secret Lives of Flow Control Structures

Flow control structures actually return values. For example, the if-else returns the last value in the executed clause:

def a := 3
def b := 4
def max := if (a > b) {a} else {b}

This behavior is most useful when used with the when-catch construct described in the When-expressions and Delayed Actions section. The break statement, when used in a for or a while loop, can be followed by an expression, in which case the loop returns the value of that expression.

Loops as Expressions

Like all structures in Monte, for loops are expressions; they return values:

def result := for value in (0..10) { value }

Here, result is null, which is the default return value for for loops. To override that value, use break:

def result := for value in (0..10) { break value }

Since break was used, the loop exits on its first iteration, returning value, which was 0. So result is 0.

List & Map Comprehensions

for loops aren’t the only way to consume iterable objects. Monte also has comprehensions, which generate new collections from iterables:

[for value in (iterable) transform(value)]

This will build and return a list. Maps can also be built with pair syntax:

[for key in (keyList) key => makeValue(key)]

And, of course, pair syntax can be used for both the pattern and expression in a comprehension:

[for key => value in (reverseMap) value => key]

Additionally, just like in Python and Haskell, comprehensions support filtering with a predicate; this is called the for-such comprehension:

>>> def evens := [for number in (1..10) ? (number % 2 == 0) number]
... evens
[2, 4, 6, 8, 10]

Just like the such-that pattern, this such-that clause is evaluated for every iteration, and iterations where the clause returns false are skipped. Also, just like the such-that pattern, and unlike some other languages’ comprehension syntax, the predicate must return a Bool; if it doesn’t, then the entire comprehension will fail with an exception.

Writing Your Own Iterables

Monte has an iteration protocol which defines iterable and iterator objects. By implementing this protocol, it is possible for user-created objects to be used in for loops and comprehensions.

Iterables need to have to _makeIterator(), which returns an iterator. Iterators need to have to next(ej), which takes an ejector and either returns a list of [key, value] or fires the ejector with any value to end iteration. Guards do not matter but can be helpful for clarity.

As an example, let’s look at an iterable that counts upward from zero to infinity:

object countingIterable:
    to _makeIterator():
        var i := 0
        return object counter:
            to next(_):
                def rv := [i, i]
                i += 1
                return rv

Since the iterators ignore their ejectors, iteration will never terminate.

For another example, let’s look at an iterator that wraps another iterator and only lets even values through:

def onlyEvens(iterator):
    return object evens:
        to next(ej):
            var rv := iterator.next(ej)
            while (rv[1] % 2 != 0):
                rv := iterator.next(ej)
            return rv

Note that the ejector is threaded through to next(ej) into the inner iterator in order to allow iteration to terminate if/when the inner iterator becomes exhausted.