Nifty chaining

Steven Schveighoffer schveiguy at yahoo.com
Mon Oct 4 06:52:21 PDT 2010


One of the warts of dcollections' API is this model:

class C(V)
{
    C add(V v, out bool wasAdded)
    C add(V v)
}

So you have the ability to chain calls to C via:

c.add(1).add(2);

And it still supports the ability to determine if the values were added.

But the API is clunky when you *do* want to know what was added, you have  
to pre-declare your variables, plus, when you are chaining, do you really  
care if each individual element was added, or do you want to know the  
effect of the entire expression?

In addition, I have various different add overloads, one of which takes a  
dynamic array.

I had wondered, wouldn't it be nice to just have this:

C add(V[] elems...)

and that would replace add(V v) as well.  But I ran into a snag, I can't  
replace this function:

C add(V[] elems, out uint numAdded)

because what if V is uint?

I thought of this idea: What I really want is the ability to chain calls,  
but also get the difference in length.  So I built this struct, and it  
actually works:

struct Chainer(T)
{
     T t;
     private size_t origlen;
     this(T t)
     {
         this.t = t;
         this.origlen = t.length;
     }

     Chainer opDispatch(string fn, Args...)(Args args) if  
(is(typeof(mixin("t." ~ fn ~ "(args)")) == T))
     {
         mixin("t." ~ fn ~ "(args);");
         return this;
     }

     @property int delta() const {return cast(int)(t.length - origlen);}
}

Chainer!T chain(T)(T t)
{
     return Chainer!T(t);
}

So here we have a struct that allows you to chain, *plus* allows you to  
get at the returned delta in length.

So you would use it like this:

auto numAdded = chain(mycollection).add(1,2,3).add(4,5,6,7).delta;

I originally wanted to just have each function that wanted to use chain  
calling return a Chainer!(typeof(this)), so you would use it like:

auto numAdded = mycollection.add(1,2,3).add(4,5,6,7).delta;

but this doesn't work for covariance.  And I use covariance everywhere to  
allow chaining no matter what the derived type is.  Although all  
dcollections classes are final, I use interfaces, and those could not be  
covariant (e.g. cannot implicitly cast Chainer!LinkList to Chainer!List).

I also originally wanted to allow implicit casting of Chainer!T to int  
which would return the given delta, but this doesn't work.  Adding 'alias  
delta this;' results in the error:

Error: no property 'add' for type 'int'

Which seems to suggest that the compiler will not try to use opDispatch  
when an alias this is present.  Does that sound like a bug or expected  
behavior?

Anyhow, the next version of dcollections will likely have these features.   
I wonder if a more generic "Chainer" type could be useful in Phobos.   
Basically, one which either accumulates some data on each chained call, or  
which determines a delta at the end.

-Steve


More information about the Digitalmars-d mailing list