Can we get rid of opApply?

Daniel Keep daniel.keep.lists at gmail.com
Mon Jan 19 15:42:39 PST 2009



Andrei Alexandrescu wrote:
> Steven Schveighoffer wrote:
>> When looking at ranges, it seems like a much better model for
>> iteration than opApply.  It seems like it could be a whole replacement
>> for opApply.  But when looking at the current usage of opApply, there
>> are some holes.
>>
>> foreach(i, x; range)
>> {
>> }
>>
>> What exactly happens?  Do you have to return a tuple from a range with
>> head()?
> 
> One possibility I'd discussed with Walter is that for the usage above,
> the compiler asks for the .key property in addition to .head. If more
> keys are specified, .key1, .key2... are required. Probably an array
> would be a nicer solution.

That's a pretty ugly solution.  It also means writing more boilerplate
to support multiple values.

I still think the simplest and cleanest way of doing this is to just let
us return tuples, already!  Hell, make the compiler explode any struct
with a .tuple member, if you have to.  I'd take that.

As for arrays... last time I checked, all the elements had to be the
same type.

>> Also, another thing that was nice about opApply, if you had multiple
>> ways to iterate over an aggregate, you simply altered your arguments
>> to foreach. How can this be implemented with ranges?
>>
>> class Aggregate
>> {
>>    int opApply(int delegate(ref int idx, ref int value) dg) {...}
>>    int opApply(int delegate(ref int value) dg) {...}
>> }
>>
>> Aggregate A = new Aggregate;
>> foreach(i, ref x; A) {...}
>> foreach(x; A) {...}
> 
> Keys will take care of that too. The "ref" thing will generate different
> code by taking the address of head (not sure if Walter implemented that).

How does taking the address of head allow you to have multiple iteration
signatures?

I think there should be a solution to this, but it's not the end of the
world if there isn't.  Python manages to make do with, for example:

  dict = {}

  foreach k in dict: pass
  foreach k in dict.iterkeys(): pass
  foreach v in dict.itervalues(): pass
  foreach k,v in dict.iteritems(): pass

Of course, that requires us to think very carefully about what the
default iteration is.  Perhaps this could be helped by allowing foreach
to simply omit leading and/or trailing values in a tuple result.

That said, the above solution would make it impossible to write
something with the same functionality as the built-in AAs.

>> Maybe ranges need some more functionality to do this.  I can't see how
>> to do it with Tuples, as you'd have to be able to overload head()
>> based on return value.  Or if a proposed opRange is supported from
>> Aggregate (opRange is called if it exists, otherwise if the aggregate
>> is a range use that, otherwise look for opApply), you'd have to
>> overload the range type returned based on usage.
> 
> Yes, I'm afraid type deduction will be harmed with ranges.

I just about exploded with this sentence, but I'm not sure I'm following
you.  To me, type deduction is this:

foreach( e ; s ) ...;

Steven's comment seems to be about having multiple iteration signatures.

Incidentally, if you ARE saying that type inference won't work with
ranges, then I'm afraid I'm going to have to take you for a scrape
'round to Dinsdale's...

>> The only thing I could think of is to change head and toe to take
>> reference parameters instead of using the return value, although
>> that's ugly.  Maybe it could be supported in addition to returning a
>> value?
> 
> [snip]
> 
>> This is ugly, and requires some compiler magic, but do you see another
>> way to do it?  I didn't know if "ref ref" would work, so that's why I
>> use pointers instead.  Since ref is a storage class, I don't know if
>> the compiler would overload anyways...
> 
> One simple solution to the overloading by return type would be to have
> head be a template. Then if you say:
> 
> foreach (int e; range) {}
> 
> the corresponding assignment for e will be:
> 
> int e = __r.head!(int)();
> 
> There are a few more wrinkles to fill with Botox though :o).
> 
> 
> Andrei

How do you then know what iteration signatures something supports, then?

-- 5 minutes later --

Thinking on this a bit before posting, I've come up with the following:

struct Tuple(T...)
{
    T tuple;
}

struct Range(T...)
{
    bool empty();
    static if( T.length == 1 )
        ref T head();
    else
        ref Tuple!(T) head();
    void next();
}

class Aggregate
{
    void opRange(out Range!(int));
    void opRange(out Range!(string));
    void opRange(out Range!(int, string));
}

void main()
{
    Aggregate z;

    foreach( ref int a ; z ) ...;
    foreach( string b ; z ) ...;
    foreach( a, b ; z ) ...;
}

Assuming the following extra conditions:

* we can use "ref T head()" to allow the values to be changed,
* opRange works (obviously), and
* the compiler will try to explode any value which has a 'tuple' member
(or any other random name you happen to like; opExplode, even) that is a
tuple of values,

that looks like it might solve the above issues.  The out-param opRange
is a bit ugly, but at least it's squirreled away in an operator overload
which users won't normally be calling.

One thing I'm worried about is that we won't be able to pass out a
direct reference to internal data; even with the "ref T head()" thing,
the range struct would need to check for a modified value, then copy it
back into its proper place. [1]

  -- Daniel

[1] I'd have said "let us return tuples" again, but I suspect that's
starting to sound like a broken record at this point.  :D



More information about the Digitalmars-d mailing list