Saturday, November 17, 2007

Ranges

Range expressions may appear, in Freya, in three different contexts:
  1. As case labels.
  2. In for statements.
  3. As the right operand of the in operator.
Of course, the original source was the case statement:
case expression of
0, 3..20, 23: Console.WriteLine('Howdy');
50..6000: Console.WriteLine('Silly');
end;
Please note that Freya, imitating C#, also supports case statements with string expressions, but in that case, ranges are not allowed, due to the underlying implementation, which depends on hash dictionaries. That's not a satisfactory situation. It could be acceptable for C#, where there're no ranges in its switch, but not for Freya. The solution, perhaps, could be the adoption of a new data structure for implementing ranges. But that's another story.

Membership

From the very first drafts, Freya featured an in operator, designed to work with collections. An expression like this:
x in A
is translated this way:
ICollection[T](A).Contains(x)
where T is the compiling time type of x. There's even a sort of "contains pattern", in the line of the "enumerable pattern", which allows any type with a suitable Contains method to be used as the right operand of the in operator.
We evaluated then several techniques for extending the in operator so it could act on ranges. The obvious technique was allowing ranges inside the brackets of array literals, imitating Pascal sets:
x in [1, 2, 5..10, 16]
After some tests, we discarded the idea. Implementing Pascalian bit sets was not an option: it would have required a core Freya assembly, and that's a scary idea. Another problem had to do with the places you could use an array literal with ranges inside. My intuition was crying aloud that the way was far from adding new data structures, like bitsets or extended enumerable interfaces.
Right now, I think ranges must exists as themselves in the language. The first hint came from the for statement. One day, I found myself writing loops like this:
for i in low .. high do
Console.WriteLine(i);
Of course it is a cheap syntactic variant of the ascending numerical for, but there was another possible interpretation: the range expression was an iterator expression, i.e., a IEnumerable[T] expression. Why shouldn't we allow expressions like this?
if i in low..high and
j not in low..high then ...
Each range check is translated as two comparisons. For instance:
// This expression...
x in e1 .. e2
// is translated as
x >= e1 and x <= e2
// and this one...
x + 1/x in e1 .. e2
// is translated as:
using temp := x + 1/x do
temp >= e1 and temp <= e2
As a side effect, we now must allow in with an IEnumerable[T] right operand... expanding it as an inline search loop. But this variant is not implemented yet.

The existence test

There's an interesting and natural variant of the membership test:
if * in collection then
// The collection is not empty.
You can also use the negated variant:
if * not in collection then
// The collection is not empty.
In two of the possible cases, the implementation is trivial. For instance, if the existence test is applied to a range, you have a simple comparison involving the boundaries:
// This expression...
* in e1 .. e2
// is translated as
e1 <= e2
When the right side implements the IList[X] generic interface, the test is based on the Count property:
// This expression...
* in list
// is translated as
list.Count > 0
However, there's a case where the existence test is really useful. The right operand may only implement the so called enumeration pattern, as in the foreach statement, or one of the IEnumerable interfaces. Let's say the right operand implements IEnumerable[X]:
// This expression...
* in enumerable
// is translated as this expression:
using e := enumerable.GetEnumerator do
e.MoveNext
Please note, first, that we have here a using expression, not a statement. Second: we need a using expression because the value returned by GetEnumerator may implement IDisposable. In that case, we need a resource protection block to dispose the enumerator after the test. Actually, our new existence test is saving you from writing a for/in statement:
var result := false;
for v in enumerable
begin
result := true;
break
end;

Labels: ,

0 Comments:

Post a Comment

<< Home