I’m working on the loop syntax in Magpie right now, and I think I more or less have the plan figured out. Looping is a bit tricky in a language: there’s a ton of different ways to do it from C’s basic for (i = 0; i < 100; i++) to Lisp’s super-powerful (and super-complex) loop macro.
My goals for loops are the same as my goals for the language in general:
- Add as few new keywords as possible.
- Use function calls for as much of it as possible.
- There must be no visible line between “baked-in” loop functions and user-defined ones.
- The most common use cases should be the most terse.
The last point is especially pertinent, and is one of the guiding philosophies of the language: the things users do the most should be the most terse. Sort of like Huffman encoding for a language.
To do that, I did a little archaeology: I looked at every place in the Magpie C# compiler where I’m using for or foreach. The results are:
- There are 14
forloops and 40foreachones. - 9 of the
forloops are to iterate through two arrays in parallel. - 3 of the
forloops are to iterate backwards. - 2 of the
forloops are to assign to array elements. - 1 of the
forloops is to iterate a certain number of times. - None of the
fororforeachloops reuse an existing variable for the current item.
From this, it’s pretty clear that I need to focus on iterating through collections (i.e. enumerators or generators), and assume that the user wants a new variable for the current item. Also, iterating through multiple collections simultaneously should be fairly easy to do. This leads me to adding just one new keyword:
for <var> <- <generator> do
...
end
In addition, multiple for clauses can be provided (but only one do) to iterate through multiple collections in parallel. I still need to work out the details, but I’m thinking that that will be syntactic sugar for:
// evaluate the generator expression once
def _generator <- Generate <generator>
// advance to the first item
_generator.MoveNext
while Not _generator.IsDone do
def <var> <- _generator.Current
...
_generator.MoveNext
end
Using that, the use cases I have can be solved by:
// iterating through a collection
for item <- someList do
Print item
end
// iterating multiple collections in lockstep
for a <- someList1
for b <- someList2 do
Print (a == b).String
end
// iterating backwards
for item <- Reverse someList
Print item
end
// assigning to array elements
for index <- array.Indexes
array.index <- 0
end
// iterating a fixed number of times
for i <- 50.Times
Print i.String
end
// iterating through a range
for i <- Range (10, 30)
Print i.String
end
In those examples, Reverse, Indexes, Times, and Range can all be simple library functions. There’s nothing magical about them. This may change when the rubber meets the road as I implement it, but it feels about right so far.
