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

Reply via email to