Thread overview
_indexed_ iteration using opApply or range
Dec 12, 2010
spir
Dec 12, 2010
Simen kjaeraas
Dec 12, 2010
Jonathan M Davis
Dec 12, 2010
spir
Dec 12, 2010
Simen kjaeraas
Dec 13, 2010
spir
December 12, 2010
Hello,

Had a nice time figuring out how to let opApply allow index iteration like:
    foreach (i, x ; collection) {}
Finally managed to do it adding 'i' everywhere:

struct S1 {
    private int[] elements = [];
    int opApply (int delegate (ref uint, ref int) block) {
        foreach (uint i, int n ; this.elements)
            block(i, n);
        return 0;
    }
}

Is this the intended idiom?
Now, I'm trying to make this work with ranges instead (input range only as of now). Seems i'm not smart enough to guess it alone...

Thank you,
Denis
-- -- -- -- -- -- --
vit esse estrany ☣

spir.wikidot.com

December 12, 2010
spir <denis.spir@gmail.com> wrote:

> Hello,
>
> Had a nice time figuring out how to let opApply allow index iteration like:
>     foreach (i, x ; collection) {}
> Finally managed to do it adding 'i' everywhere:
>
> struct S1 {
>     private int[] elements = [];
>     int opApply (int delegate (ref uint, ref int) block) {
>         foreach (uint i, int n ; this.elements)
>             block(i, n);
>         return 0;
>     }
> }
>
> Is this the intended idiom?

opApply is a strange beast. Your version does not handle early returns
or other errors, for which block's return type should be checked:

int opApply( int delegate( ref uint, ref int ) block ) {
    foreach ( uint i, int n; this.elements ) {
        if ( auto ret = block( i, n ) != 0 ) {
            return ret;
        }
    }
    return 0;
}

As for the i, yes, you have to do it like that. In fact, you can have as
many parameters you want for the delegate, and refer to them in the foreach:

struct foo{
    int opApply( T... )( int delegate( ref T ) dg ) {
        return 0;
    }
}


foo f;
foreach ( int i, double d, string s; f ) {}


> Now, I'm trying to make this work with ranges instead (input range only as of now). Seems i'm not smart enough to guess it alone...

The trick to ranges is that they modify themselves. For a simple array wrapper
range this may be a way:

struct wrapper( T ) {
    T[] data;
    void popFront( ) {
        data = data[1..$];
    }
    ref T front( ) {
        return data[0];
    }
    bool empty( ) {
        return data.length == 0;
    }
}

Feel free to ask if you wonder about anything specific.

-- 
Simen
December 12, 2010
On Sunday 12 December 2010 12:15:00 Simen kjaeraas wrote:
> spir <denis.spir@gmail.com> wrote:
> > Hello,
> > 
> > Had a nice time figuring out how to let opApply allow index iteration
> > 
> > like:
> >     foreach (i, x ; collection) {}
> > 
> > Finally managed to do it adding 'i' everywhere:
> > 
> > struct S1 {
> > 
> >     private int[] elements = [];
> >     int opApply (int delegate (ref uint, ref int) block) {
> > 
> >         foreach (uint i, int n ; this.elements)
> > 
> >             block(i, n);
> > 
> >         return 0;
> > 
> >     }
> > 
> > }
> > 
> > Is this the intended idiom?
> 
> opApply is a strange beast. Your version does not handle early returns or other errors, for which block's return type should be checked:
> 
> int opApply( int delegate( ref uint, ref int ) block ) {
>      foreach ( uint i, int n; this.elements ) {
>          if ( auto ret = block( i, n ) != 0 ) {
>              return ret;
>          }
>      }
>      return 0;
> }
> 
> As for the i, yes, you have to do it like that. In fact, you can have as many parameters you want for the delegate, and refer to them in the foreach:
> 
> struct foo{
>      int opApply( T... )( int delegate( ref T ) dg ) {
>          return 0;
>      }
> }
> 
> 
> foo f;
> foreach ( int i, double d, string s; f ) {}
> 
> > Now, I'm trying to make this work with ranges instead (input range only as of now). Seems i'm not smart enough to guess it alone...
> 
> The trick to ranges is that they modify themselves. For a simple array
> wrapper
> range this may be a way:
> 
> struct wrapper( T ) {
>      T[] data;
>      void popFront( ) {
>          data = data[1..$];
>      }
>      ref T front( ) {
>          return data[0];
>      }
>      bool empty( ) {
>          return data.length == 0;
>      }
> }
> 
> Feel free to ask if you wonder about anything specific.

I'd also point out that the correct type for indexing is generally size_t. That's what arrays use. I believe that size_t _is_ uint on 32 bit machines, but it's going to be ulong on 64 bit machines. So, using uint is not portable.

- Jonathan M Davis
December 12, 2010
On Sun, 12 Dec 2010 21:15:00 +0100
"Simen kjaeraas" <simen.kjaras@gmail.com> wrote:

> The trick to ranges is that they modify themselves. For a simple array
> wrapper
> range this may be a way:
> 
> struct wrapper( T ) {
>      T[] data;
>      void popFront( ) {
>          data = data[1..$];
>      }
>      ref T front( ) {
>          return data[0];
>      }
>      bool empty( ) {
>          return data.length == 0;
>      }
> }
> 
> Feel free to ask if you wonder about anything specific.

Thank you, Simen. 2 comments:

(1) In my case, I do not want to modify the range (actually the private collection), because the type is not only about beeing a range (~ provide iteration). So that I defined the range methods, using a private 'rangeIndex slot', as (the type also maintains a 'length' slot):

    private uint rangeIndex;
    @property void popFront () {
        // (re)start...
        if (this.rangeIndex >= this.length)
            this.rangeIndex = 0;
        // ...or move on
        else
            ++ this.rangeIndex;
    }
    @property bool empty () {
        return (this.rangeIndex >= this.length);
    }
    @property T front () {
        return this.stacks[this.rangeIndex];
    }

Note that this also enables a restarting feature :-)

(2) How then can I introduce indexed iteration (or more generally iteration with more than one parameter to the block), using ranges? The blocking (!) point is there is no way for front() to return a several multiple results.
I can cheat using a tuple or struct, but D won't understand and unpack it for the user :-) Sure, the user may know I return a pack, but...


Denis
-- -- -- -- -- -- --
vit esse estrany ☣

spir.wikidot.com

December 12, 2010
spir <denis.spir@gmail.com> wrote:

> (1) In my case, I do not want to modify the range (actually the private collection), because the type is not only about beeing a range (~ provide iteration). So that I defined the range methods, using a private 'rangeIndex slot', as (the type also maintains a 'length' slot):
>
>     private uint rangeIndex;
>     @property void popFront () {
>         // (re)start...
>         if (this.rangeIndex >= this.length)
>             this.rangeIndex = 0;
>         // ...or move on
>         else
>             ++ this.rangeIndex;
>     }
>     @property bool empty () {
>         return (this.rangeIndex >= this.length);
>     }
>     @property T front () {
>         return this.stacks[this.rangeIndex];
>     }
>
> Note that this also enables a restarting feature :-)

In this case, you probably want to use the idiom I described in the other
thread, that opSlice() return a range struct. Consider:

foreach ( i, e; myRange ) {
    if ( i == 3 ) break;
}

foreach ( i, e; myRange ) {
    // What is i?
}

For the restarting feature, forward ranges expose a function save(),
which returns a copy of the range as it was at the time of save() being
called. In many cases, save's body is simply 'return this;'.


> (2) How then can I introduce indexed iteration (or more generally iteration with more than one parameter to the block), using ranges? The blocking (!) point is there is no way for front() to return a several multiple results.
> I can cheat using a tuple or struct, but D won't understand and unpack it for the user :-) Sure, the user may know I return a pack, but...

I know. There is currently no solution to this except opApply.

-- 
Simen
December 13, 2010
On Sun, 12 Dec 2010 14:47:04 -0500, spir <denis.spir@gmail.com> wrote:

> Hello,
>
> Had a nice time figuring out how to let opApply allow index iteration like:
>     foreach (i, x ; collection) {}
> Finally managed to do it adding 'i' everywhere:
>
> struct S1 {
>     private int[] elements = [];
>     int opApply (int delegate (ref uint, ref int) block) {
>         foreach (uint i, int n ; this.elements)
>             block(i, n);
>         return 0;
>     }
> }
>
> Is this the intended idiom?
> Now, I'm trying to make this work with ranges instead (input range only as of now). Seems i'm not smart enough to guess it alone...

It's not possible.  There is no mechanism for foreach to use an 'index' field from the range.

What's wrong with using opApply?  You should be able to define both range primitives and opApply and opApply will be used when foreach is used, and the range primitives will be used by things like std.algorithm.

-Steve
December 13, 2010
On Mon, 13 Dec 2010 09:47:39 -0500
"Steven Schveighoffer" <schveiguy@yahoo.com> wrote:

> What's wrong with using opApply?  You should be able to define both range primitives and opApply and opApply will be used when foreach is used, and the range primitives will be used by things like std.algorithm.

Hope opApply would take precedence for iteration. I like range interface for its integration with other language features, like funcs from std.algorithm precisely. May end up using both (if works properly -- i have other issues with ranges).

Denis
-- -- -- -- -- -- --
vit esse estrany ☣

spir.wikidot.com