Wednesday, May 30, 2007

 

Iterators in Flan

One of the nicest features of Flan is its support for iteration. Here's an example:

#println( greetings[each] );

Even if you haven't seen the syntax before (and you haven't, because I made it up), I think this should be fairly self-explanatory. It takes a list of strings called "greetings", and prints each one (with a newline at the end, thanks to println). So if you start with a declaration such as...

@greetings = [ "Hello world!", "Goodbye world!", "Don't forget to write, world!" ];

...then the result is:

Hello world!
Goodbye world!
Don't forget to write, world!



Semantically, this has the same effect as rearranging the line into a C#-style foreach loop.

foreach( string g in greetings )
{
println(g);
}


(The above is pseudo-C#, not real Flan code).
Ok then, one question remains - what's with the # at the beginning of the line?

#println( greetings[each] );

Well, it's called the multiple execution operator, and all uses of [each] must be contained in one: otherwise the compiler will complain.

This operator does two jobs: firstly, it serves as a visual warning to people reading the code, "careful, this line is not as simple as it looks". (That's why it's such a big black character.)

Secondly, it's a merge point, letting you move code out of the foreach expression. Code that's syntactically "outside" the # will be executed only once, taking the list of values as a whole instead of each individual value. Hard to describe, easy to show by example:

@vec = [44, 62, 10];
@veclength = sqrt( sum( #square(vec[each]) ));

This is calculating the length of the vector vec. The important bit is the expression #square(vec[each]), which evaluates to a new list: [square(44), square(62), square(10)]. The sum function then takes the sum of those items, and so on.


Useful enough yet? We're just getting started. You can also filter the list this way, with the ~ operator. Read it as "that is".
For example, to print the integers in a list:

#println( thelist[each] ~int );

And as you may recall a Flan type can be, in fact, any value or predicate:

#println( thelist[each] ~even );
#println( thelist[each] ~prime );
#println( thelist[each] ~1 ));
#println( thelist[each] ~ >0 ) );
#println( thelist[each] ~divisibleby(7) );
#println( thelist[each] ~[3] ) );
#println( thelist[each] ~[char*, 'hello', char*] );

You can also filter by index, by putting a type inside the brackets instead of outside. For example, to select alternate items in the list:

#println( thelist[each ~even] );

Or the first 10 items:

#println( thelist[each ~ <10] );

Flan's declare-anywhere syntax also comes in handy here. You can name the index, and refer back to it later. (For obvious reasons, identifiers declared this way will go out of scope when the loop ends.)

For example, search for items in the list that are greater than their index.

#println( thelist[each @i] ~ >i );

Or, borrowing an example from my last post, you can iterate over two lists at the same time.

@dot_ab = sum( #a[each @i] * b[i] );

(In case you're wondering, the # operator has the lowest precedence of any operator.)



In much the same way as the indices, you can name the values. For example, in the vector length example from earlier, instead of calling square, you could write:

@veclength = sqrt( sum( #thelist[each] @v * v ));



And finally, it sometimes happens that you're not interested in the whole list - 'each' is not the behaviour you want. If you just want to find one item, say 'find' instead:

@x = #thelist[find] ~prime;

(NB: rather than evaluating to a list of matches, a # expression controlled by find will return a single value. If the list contains no prime numbers, it will return null.)


Another type of iteration control is every. This is a logical AND applied to every member of the list, shortcutting at the first false result. This is mostly useful in the context of an 'if' statement. For example, to test whether every entry in the list is positive:

if( #thelist[every].is(>0) ) { ... }

And of course where you have an AND, you sometimes need OR...

if( #thelist[some].is(>0) ) { ... }



That's all for now. Hope this has been interesting.

Oh, and since nobody will take a language design seriously if there's no compiler that runs it, I'm working on changing my interpreter into a compiler that will generate CIL, via the handy Cecil library. This seems like the ideal compilation target for any nascent language - debuggable, (slightly) portable, and with a load of predefined libraries. I'll let you know how the conversion goes.



This page is powered by Blogger. Isn't yours?