Looping
TODO: This needs to be rewritten now that while
and for
are distinct.
Many languages have three distinct loop structures: for
, while
, and foreach
or something similar for generators. Magpie only has a single loop construct, but it subsumes all three of those. A simple loop looks like this:
while something do body end
The while something
part is the clause, and the expression after do
(here a block) is the body. The clause is evaluated at the beginning of each loop iteration (including before the first). If it fails, the loop body is skipped and the loop ends. There are two kinds of clauses:
while
Clauses
The simplest is while
. It evaluates a condition expression. If it evaluates to false
, the loop ends, otherwise it continues. For example:
while 1 < 2 do print("forever...")
for
Clauses
The other loop clause is for
. It looks like:
for item in collection do print(item)
The expression after in
is evaluated once before the loop starts and is expected to return an iterable object, which it then passes to the iterate
method. That should return an iterator.
An iterator generates a series of values. There are two methods it gets passed to. The next()
method is called before each loop iteration (including the first) and advances the iterator to its next position. If the iterator is out of values, it returns false
and the clause fails, otherwise it returns true
. The current
getter returns the current value that the iterator is sitting on.
Each iteration of the loop, Magpie will advance the iterator and bind the current value to a variable. The variable is scoped within the body of the loop. What this means is that a loop like:
for item in collection do print(item)
Is really just syntactic sugar for something like:
val __iter = collection iterate() while __iter next do val item = __iter current print(item) end
All of this means that Magpie only has a foreach
-like for loop. It doesn't have a primitive C-style for
loop. To get the classic "iterate through a range of numbers" behavior, the standard library provides methods on numbers that return iterators:
for i = 1 to(5) do print(i) // Prints "1", "2", "3", "4", "5". for i = 1 until(5) do print(1) // Prints "1", "2", "3", "4".
Here, to
and until
are just regular methods and not built-in keywords. They each return ranges that iterate through a series of numbers.
Combining Clauses
The reason Magpie has only a single loop expression is because clauses can be combined. A single loop can have as many clauses as you want, of either kind, in any order. At the beginning of each iteration of a loop (including before the first one), all of the clauses are evaluated in the order they appear. If any clause fails, the loop body is skipped and the loop ends. For example:
while happy while knowIt do clap(hands) end
Once one of those while
clauses returns false, the loop ends. Note that the clauses are iterated in parallel, unlike nested loops:
for i = 1 to(3) for j = 6 to(10) do print(i + ":" + j) end // Prints "1:6", "2:7", "3:8". for i = 1 to(4) do for j = 6 to(10) do print(i + ":" + j) end end // Prints "1:6", "1:7", "1:8", "1:9", "1:10", "2:7" ... "4:10".
The key difference is do
. That's the keyword that indicates the end of the
clauses and the beginning of the body. No matter how many clauses you have, a
single do
means a single loop.
Allowing multiple for
clauses like this enables some handy idioms:
// Iterate through a collection with indexes for i = 0 countingUp for item = collection do // Here 'item' is the item and 'i' is its index. end // Iterate through the first 10 items in a collection. for i = 1 to(10) for item = collection do print(item) // Iterate through a collection until an item is found. var found = nothing while found == nothing for item = collection do if test(item) then found = item end
Exiting Early
Inside the body of a loop, you can exit early using a break
expression:
while true do something if badNews then break somethingElse end
Return Value
Because loops are expressions, they return a value.
Right now, a loop will always return `nothing`, but that will likely change. It will be possible to define an `else` clause for a loop and then the expression will return the last result of evaluating the body, the expression passed to `break` or the expression after `else` if the body was never entered.