The future of foreach

Janice Caron caron800 at googlemail.com
Sun Dec 23 03:18:42 PST 2007


Walter has stated many times that foreach is a good thing because it
expresses the programmer's intent, and leaves the optimisation down to
the compiler. (Should it use pointers? Should it use indeces? etc.)

I agree with him.

However, it is sadly flawed in that you can't iterate through two
collections in lockstep. I'm sure that many suggestions have been
proposed in the past to work around this limitation, but the bottom
line has always been that we're stuck with opApply(), and opApply()
cannot be made to loop through two things at once.

So...

I'd like to suggest a /gradual/ change. It seems to me that this would
work without really hurting anything, and programmers could get used
to new idioms a little bit at a time.

STEP ONE - Make it work for built-in arrays /only/

This one seems pretty straightforward. For built-in arrays, we allow
people to do this:

    int[] a, b, c;
    foreach(ref x;a)(y;b)(z;c) { x = y * z; }

This should present the compiler with no difficulty, because we're
/only/ talking about builtin arrays here, and so there's no opApply()
to worry about.

This will also give us coders a chance to play with it and get used to
the idiom.

At this point, /some/ structs and classes will be able to add their
own elementwise features simply by providing a function which returns
an array. For example:

    Vector!(10,int) a,b,c;
    foreach(ref x;a.toArray)(y;b.toArray)(z;c.toArray) { x = y * z; }

It's not perfect (yet), but it's a step in the right direction.

STEP TWO - Allow foreach to recurse into multidimensional arrays

This is a pretty nice one.

    int[][] a;
    foreach(int[] x;a) { /*elements of a*/ }
    foreach(int x; a) { /* elements of elements of a*/ }

Now we'll be able to add elementwise features to even more structs and
classes. For example:

    Matrix!(10,10,int) a,b,c;
    foreach(ref int x;a.toArray)(int y;b.toArray)(int z;c.toArray) { x
= y * z; }

(Yes, I'm aware that that's not doing matrix multiplication, but
apparently there is a need to do this). Again, it's not perfect (yet),
but it's moving just a little bit closer.

STEP THREE - Extend these features to "array-like types".

If we consider an "array-like type" to be any class or struct which implements:

    opIndex()
    opIndexAssign()
    length()

and/or

    ptr()
    end()

(with the latter two returning iterators), then I see no reason why
these features couldn't also be made to work with arbitrary
collections. The rule would be:

    (1) if we implement opIndex(), opIndexAssign() and length(), use those, else
    (2) if we implement ptr() and end(), use those, else
    (3) if we implement opApply(), use that (with all the old limitations), else
    (4) compile-time error

Of course, we don't have iterators yet, so I should probably have
added step 2.5, finish implementiing iterators. We already have /most/
of what iterators need: opEquals(), opPostInc(), opPostDec() and
opStar() (hopefully to be renamed opDeref()). I think we're still
missing opStarAssign() / opDerefAssign(), but once that's in place
we'd be good to go.

Once step three is in place, structs and classes will no longer need a
toArray() function, and (better still) the mechanism will work even
for collections which /can't/ return an array, such as linked lists.
At this point we'll be able to do

    List!(Widget) a,b,c;
    foreach(ref x;a)(y;b)(z;c) { a = b.someFunction(c); }

Thoughts?



More information about the Digitalmars-d mailing list