www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - opApply and const

reply Bill Baxter <dnewsgroup billbaxter.com> writes:
I mentioned it in another thread but I just wanted to confirm it.
Am I right in thinking that if we want to have user types act like 
built-in containers w.r.t iteration in D2 then we need to implement 12 
different versions of opApply?!

Namely:

// plain vanilla
int opApply(int delegate(ref T)) {...}
const int opApply(int delegate(ref const T)) {...}
invariant int opApply(int delegate(ref invariant T)) {...}

// with counter
int opApply(int delegate(ref size_t, ref T)) {...}
const int opApply(int delegate(ref size_t, ref const T)) {...}
invariant int opApply(int delegate(ref size_t, ref invariant T)) {...}

// backwards
int opApplyReverse(int delegate(ref T)) {...}
const int opApplyReverse(int delegate(ref const T)) {...}
invariant int opApplyReverse(int delegate(ref invariant T)) {...}

// backwards with counter
int opApplyReverse(int delegate(ref size_t, ref T)) {...}
const int opApplyReverse(int delegate(ref size_t, ref const T)) {...}
invariant int opApplyReverse(int delegate(ref size_t, ref invariant T)) 
{...}

That's a lot of opApply!  I was already mildly annoyed by the four 
needed without const.  But with both const (and invariant) it just seems 
downright silly.

--bb
Dec 08 2007
next sibling parent Robert Fraser <fraserofthenight gmail.com> writes:
Bill Baxter wrote:
 I mentioned it in another thread but I just wanted to confirm it.
 Am I right in thinking that if we want to have user types act like 
 built-in containers w.r.t iteration in D2 then we need to implement 12 
 different versions of opApply?!
 
 Namely:
 
 // plain vanilla
 int opApply(int delegate(ref T)) {...}
 const int opApply(int delegate(ref const T)) {...}
 invariant int opApply(int delegate(ref invariant T)) {...}
 
 // with counter
 int opApply(int delegate(ref size_t, ref T)) {...}
 const int opApply(int delegate(ref size_t, ref const T)) {...}
 invariant int opApply(int delegate(ref size_t, ref invariant T)) {...}
 
 // backwards
 int opApplyReverse(int delegate(ref T)) {...}
 const int opApplyReverse(int delegate(ref const T)) {...}
 invariant int opApplyReverse(int delegate(ref invariant T)) {...}
 
 // backwards with counter
 int opApplyReverse(int delegate(ref size_t, ref T)) {...}
 const int opApplyReverse(int delegate(ref size_t, ref const T)) {...}
 invariant int opApplyReverse(int delegate(ref size_t, ref invariant T)) 
 {...}
 
 That's a lot of opApply!  I was already mildly annoyed by the four 
 needed without const.  But with both const (and invariant) it just seems 
 downright silly.
 
 --bb

It's not just opApply; any function would need to be written three times for accessing constant data in a possibly mutable class.
Dec 09 2007
prev sibling next sibling parent reply "Janice Caron" <caron800 googlemail.com> writes:
On 12/9/07, Bill Baxter <dnewsgroup billbaxter.com> wrote:
 const int opApply(int delegate(ref const T)) {...}

And "ref const T" seems a bit of an oxymoron anyway. I can bring it down to eight. You shouldn't have to deal with invariant classes. These can't really exist in D, since invarant C = new C; will fail to compile, and there is no "inew". But yeah - eight versions of the same function is too much. Is there any way we can narrow it down to one? (One that actually works, in all eight cases?)
Dec 09 2007
parent Robert Fraser <fraserofthenight gmail.com> writes:
Janice Caron wrote:
     invarant C = new C;
 
 will fail to compile, and there is no "inew".

invariant C = cast(invariant) new C();
Dec 09 2007
prev sibling next sibling parent "Janice Caron" <caron800 googlemail.com> writes:
On 12/9/07, Robert Fraser <fraserofthenight gmail.com> wrote:
 Janice Caron wrote:
     invarant C = new C;

 will fail to compile, and there is no "inew".

invariant C = cast(invariant) new C();

I didn't forget that, I just didn't see the point of it. What I did forget, though, is invariant structs. Now they do make sense! And surely opApply has to work for those, too. So we're back up to twelve again!
Dec 09 2007
prev sibling next sibling parent reply Daniel Keep <daniel.keep.lists gmail.com> writes:
Bill Baxter wrote:
 I mentioned it in another thread but I just wanted to confirm it.
 Am I right in thinking that if we want to have user types act like
 built-in containers w.r.t iteration in D2 then we need to implement 12
 different versions of opApply?!
 
 Namely:
 
 [snip]
 
 That's a lot of opApply!  I was already mildly annoyed by the four
 needed without const.  But with both const (and invariant) it just seems
 downright silly.
 
 --bb

There's really no need for the counter versions. Just solve this the same way Python did: with a wrapper. Admittedly, this doesn't seem to work for more than one argument (foreach appears to dislike tuples for arguments), but it illustrates the general concept. -- Daniel module enumerate; import std.stdio; import std.traits; struct Enumerate(T) { private { T source; alias ParameterTypeTuple!(typeof(T.opApply))[0] opApply_arg; alias ParameterTypeTuple!(opApply_arg) opApply_type; } int opApply(int delegate(ref size_t, opApply_type) dg) { int result = 0; size_t i = 0; foreach( x ; source ) { if( (result=dg(i,x)) != 0 ) break; ++i; } return result; } } Enumerate!(T) enumerate(T)(T source) { return Enumerate!(T)(source); } class Foo { static const words = ["On"[],"a","Sunday,","riding","my","bike"]; int opApply(int delegate(ref char[]) dg) { int result = 0; foreach( word ; words ) if( (result=dg(word)) != 0 ) break; return result; } } void main() { foreach( word ; new Foo ) writef("%s ",word); writefln(""); writefln(""); foreach( i,word ; enumerate(new Foo) ) writefln("[%s] %s", i, word); }
Dec 09 2007
parent reply Daniel Keep <daniel.keep.lists gmail.com> writes:
My apologies.  It *does* actually support arbitrary opApply()s.  What
probably *doesn't* support are types with more than one opApply.  Can we
get a list of overloads with D 2.0 yet?

	-- Daniel

module enumerate;

import std.stdio;
import std.traits;

struct Enumerate(T)
{
    private
    {
        T source;
        alias ParameterTypeTuple!(typeof(T.opApply))[0] opApply_arg;
        alias ParameterTypeTuple!(opApply_arg) opApply_type;
    }

    int opApply(int delegate(ref size_t, opApply_type) dg)
    {
        int result = 0;
        size_t i = 0;

        foreach( ref opApply_type x ; source )
        {
            if( (result=dg(i,x)) != 0 ) break;
            ++i;
        }
        return result;
    }
}

Enumerate!(T) enumerate(T)(T source)
{
    return Enumerate!(T)(source);
}

class Foo
{
    static const words = ["On"[],"a","Sunday,","riding","my","bike"];

    int opApply(int delegate(ref char[]) dg)
    {
        int result = 0;
        foreach( word ; words )
            if( (result=dg(word)) != 0 ) break;
        return result;
    }
}

class Bar
{
    int opApply(int delegate(ref real, ref char[]) dg)
    {
        // Nasty code:
        real r; char[] d;
        r = 3.14159;    d = "pi"; dg(r,d);
        r = 3.0;        d = "pi (according to the Bible)"; dg(r,d);
        r = 42.0;       d = "meaning of life"; dg(r,d);
        r = real.nan;   d = "how much wood would a wood chuck chuck?";
dg(r,d);
        return 0;
    }
}

void main()
{
    foreach( word ; new Foo )
        writef("%s ",word);
    writefln("");

    writefln("");

    foreach( i,word ; enumerate(new Foo) )
        writefln("[%s] %s", i, word);

    writefln("");

    foreach( r,desc ; new Bar )
        writefln("%s: %s", r, desc);

    writefln("");

    foreach( i,r,desc ; enumerate(new Bar) )
        writefln("[%s] %s: %s", i, r, desc);
}
Dec 09 2007
next sibling parent Christopher Wright <dhasenan gmail.com> writes:
Daniel Keep wrote:
 My apologies.  It *does* actually support arbitrary opApply()s.  What
 probably *doesn't* support are types with more than one opApply.  Can we
 get a list of overloads with D 2.0 yet?
 
 	-- Daniel

In the general case: no. If you're only concerned about the virtual ones: yes, with __traits(getVirtualFunctions). (I think that's it; I always forget what it is exactly.)
 module enumerate;
 
 import std.stdio;
 import std.traits;
 
 struct Enumerate(T)
 {
     private
     {
         T source;
         alias ParameterTypeTuple!(typeof(T.opApply))[0] opApply_arg;
         alias ParameterTypeTuple!(opApply_arg) opApply_type;
     }
 
     int opApply(int delegate(ref size_t, opApply_type) dg)
     {
         int result = 0;
         size_t i = 0;
 
         foreach( ref opApply_type x ; source )
         {
             if( (result=dg(i,x)) != 0 ) break;
             ++i;
         }
         return result;
     }
 }
 
 Enumerate!(T) enumerate(T)(T source)
 {
     return Enumerate!(T)(source);
 }
 
 class Foo
 {
     static const words = ["On"[],"a","Sunday,","riding","my","bike"];
 
     int opApply(int delegate(ref char[]) dg)
     {
         int result = 0;
         foreach( word ; words )
             if( (result=dg(word)) != 0 ) break;
         return result;
     }
 }
 
 class Bar
 {
     int opApply(int delegate(ref real, ref char[]) dg)
     {
         // Nasty code:
         real r; char[] d;
         r = 3.14159;    d = "pi"; dg(r,d);
         r = 3.0;        d = "pi (according to the Bible)"; dg(r,d);
         r = 42.0;       d = "meaning of life"; dg(r,d);
         r = real.nan;   d = "how much wood would a wood chuck chuck?";
 dg(r,d);
         return 0;
     }
 }
 
 void main()
 {
     foreach( word ; new Foo )
         writef("%s ",word);
     writefln("");
 
     writefln("");
 
     foreach( i,word ; enumerate(new Foo) )
         writefln("[%s] %s", i, word);
 
     writefln("");
 
     foreach( r,desc ; new Bar )
         writefln("%s: %s", r, desc);
 
     writefln("");
 
     foreach( i,r,desc ; enumerate(new Bar) )
         writefln("[%s] %s: %s", i, r, desc);
 }

Dec 09 2007
prev sibling parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Daniel Keep wrote:
 My apologies.  It *does* actually support arbitrary opApply()s.  What
 probably *doesn't* support are types with more than one opApply.  Can we
 get a list of overloads with D 2.0 yet?

I like it (no surprise since I also like Python). How about reverse? Can you cook up a reversed() template that would use an opApplyReverse if it exists or else forward iterate into a buffer and reverse iterate over that? Or maybe creating a big temp buffer silently isn't such a hot idea...
Dec 09 2007
parent Daniel Keep <daniel.keep.lists gmail.com> writes:
Bill Baxter wrote:
 Daniel Keep wrote:
 My apologies.  It *does* actually support arbitrary opApply()s.  What
 probably *doesn't* support are types with more than one opApply.  Can we
 get a list of overloads with D 2.0 yet?

I like it (no surprise since I also like Python). How about reverse? Can you cook up a reversed() template that would use an opApplyReverse if it exists or else forward iterate into a buffer and reverse iterate over that? Or maybe creating a big temp buffer silently isn't such a hot idea...

Well, this is how Python does it, I believe. The position there appears to be: better to be possible and inefficient than not possible at all. Really, I think it's somewhat surprising that D doesn't have *any* iteration tools whatsoever. If I weren't already so busy with other things, I'd be tempted to write some myself. And there's always the chance that downs has written stuff already (that no one else can read without screaming in pain.) ;) Still, I think one of the biggest hurdles to writing a good iterator toolkit is how opApply is implemented in D. Because of the callback design, you can't do anything with resumable iterators, you can't do multiple iterations in lockstep... it's a bit of a pity that opApply is so firmly entrenched in the language. Oh well; I suppose there's always D 3.0. :) -- Daniel
Dec 09 2007
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Bill Baxter" wrote
I mentioned it in another thread but I just wanted to confirm it.
 Am I right in thinking that if we want to have user types act like 
 built-in containers w.r.t iteration in D2 then we need to implement 12 
 different versions of opApply?!

Note that there is no requirement to implement the invariant versions. An invariant object can be implicitly cast to a const object, and so the const versions can be called by invariant objects. The only benefit of having an invariant AND a const version is to skip things like mutex locking in the invariant version. I've been thinking about this for a while, and I've come to the conclusion that I think invariant and const functions are so damned similar, that there must be a way to specify them both at once. I mean, if the only difference between an invariant and a const is you don't have to do mutex locks to read data, can't the compiler just make 2 versions of a const function, and not do the synchronized(X) where X is invariant? It'd be like an automatic template, where any time you specify a const parameter (including 'this'), it could also generate a function where the const parameter is invariant. I know Walter wants to promote invariant functions as a way to help avoid thread mistakes, but I highly doubt I will be a common practice, especially by people who are not familiar with the perils of threading. This seems to me to be similar to inline, where the compiler would know better than me when it could optimize by calling an invariant version. -Steve
Dec 10 2007