Brad Roberts wrote:
Since take is a mutator, that implies that h needs to be a reference
type or a by ref value, right?  So, why not make it so?

That's a great question, and one that I don't have a definitive answer to. There are several reasons for which by-ref manipulation is dicey:

1. Composition is harder.

Consider:

foreach (e; take(10, map!("a*a")(range))) ...

In this case, take is passed an rvalue. Rvalues can't and shouldn't bind to references (I think C++'s rule of binding rvalues to const references was a subtle disaster that necessitated endless contortions in defining rvalue references).

So then we need to explicitate:

auto t = map!("a*a")(range);
foreach (e; take(10, t)) ...

2. Difficulties in coding.

My first attempt at ranges was in some formatting code. A by-ref doctrine requires that "ref" is added religiously to the trafficked values. That's not much of a problem except that, if you forget, the code still compiles and runs as told, except that the way it's told is not the way you meant.

3. More difficulties in coding

Coding with more non-local dependencies is harder than coding that creates new, relatively independent entities. The examples given so far were as simple as:

int[] a = [ 1, 2, 3 ];
take(1, a);
// WHY IS A NOT [2, 3] NOW???

In reality, code is almost never that simple and the reality is that it becomes exceedingly harder to assess the state of a range after several other lazy ranges mutate it at their leisure.

That's what I have so far.

On the other hand, other functions are a tougher call. Consider "drop". drop(n, range) throws away at most n elements from the range. There are a few possible ways to define drop:

a) By reference
int[] a = [ 1, 2, 3 ];
drop(1, a);
assert(a == [ 2, 3]);

b) By value
int[] a = [ 1, 2, 3 ];
drop(1, a);
assert(a == [ 1, 2, 3]);
a = drop(1, a);
assert(a == [ 2, 3]);

Unlike take, drop is eager, hence its by-ref implementation doesn't raise as many problems. I'm not sure what's the best choice for drop.


Andrei

Reply via email to