output ranges: by ref or by value?

Andrei Alexandrescu SeeWebsiteForEmail at erdani.org
Sat Jan 2 16:51:41 PST 2010


Steven Schveighoffer wrote:
> On Sat, 02 Jan 2010 13:51:48 -0500, Andrei Alexandrescu 
> <SeeWebsiteForEmail at erdani.org> wrote:
> 
>> Steven Schveighoffer wrote:
>>>  Would it not be as useful then to just define attributes on the type 
>>> and save a bunch of useless/weird looking code?
>>>  that is, have an enum value inside the type that defines its state 
>>> can be saved.  It seems to me like save is the equivalent of that 
>>> anyways, since its possible to forget to use it, and still have your 
>>> code compile.
>>
>> If you have an enum that says a range's state can be saved, then you 
>> still need a function to effect that save :o). So you added, not 
>> removed, bloat.
> 
> The function already exists -- opAssign.  My point is that save would 
> just be the same as opAssign in most cases where you'd want to implement 
> save.  Cases where you'd want to implement save differently than 
> opAssign are cases where you wouldn't want to use it in a generic fashion.

You might mean this(this). Either doesn't help because you'd still be 
unable to differentiate between input ranges and forward ranges. Much of 
the point of save() is to mark a syntactic difference between input 
ranges and forward ranges. Input ranges still need this(this) and 
opAssign - those just have different semantics.

>>> Basically, it appears to me that save either a) doesn't compile or b) 
>>> is the equivalent of assignment.  It doesn't seem to add much to the 
>>> functionality.
>>
>> No, for class ranges save() is nontrivial (a sort of clone). I suspect 
>> even struct ranges for certain file-oriented stuff would do nontrivial 
>> work (e.g. open the file anew and fseek() on the current position etc.)
> 
> Classes should not be ranges.  And I hope that any algorithm that uses 
> savable ranges would not be used on a file range.  For example, your 
> consume function:
> 
> bool consume(R1, R2)(ref R1 r1, R2 r2)
>         if (isForwardRange!R1 && isInputRange!R2)
> {
>     auto r = r1.save();  // open an extra file, and seek the file to the 
> given point *just in case*?!
>     while (!r2.empty && !r.empty && r.front == r2.front) {
>         r.popFront();
>         r2.popFront();
>     }
>     if (r2.empty) {
>         r1 = r;  // note that this unaliases r1 from any other copies 
> (not save()'d copies) that were made of it.
>                  // also note that you now have opened more handles to 
> the same file.  Calling this many times could
>                  // consume quite a few handles.
>         return true;
>     }
>     return false;
> }
> 
> Here is a good exercise that would help clarify what we need: determine 
> all the types of ranges you can think of that would need a special save 
> function.

A range that defines save() is a forward range. save() creates an 
independent range from its source. The file etc. example was 
hypothetical but realistic.

>>> This is all except for classes, which I think have no business being 
>>> ranges in the first place.
>>
>> Well then how would the container hierarchy work? It does need range 
>> interfaces. How does dcollections deal with that?
> 
> A container is not a range.  A range of a container should not be a 
> class.  For dcollections, I was planning on ranges being a struct with a 
> begin and end cursor defining the part of the container referenced by 
> the range.  Such a range can always be copied and modifying the copy 
> through the range functions should not modify the original range.

Struct ranges won't work with a container hierarchy. If you define a 
container hierarchy (classes etc.) you'll also need a range hierarchy. 
Otherwise defining the inheritance relationship is impossible. Consider:

abstract class Container(E) { // most general container
     @property bool empty();
     bool add(E element);
     E removeAny();
     InputRange!E opSlice();
}

That's what I'd expect any container worth its salt to implement: (a) 
test for empty; (b) add an element to the container; (c) remove some 
element from the container; (d) get a range that spans the entire 
container. (Probably removeAll() and a range-positioned remove() would 
make sense, too.)

My point is, how do you inherit this guy? Well by taking advantage of 
covariant return types:

abstract class Array(E) : Container!E {
     @property bool empty();
     bool add(E element);
     E removeAny();
     RandomAccessRange!E opSlice();
     ... more stuff ...
}

Ergo, RandomAccessRange!E must inherit InputRange!E for covariance to 
kick in. The resulting setup is quite interesting: you either know you 
work with an Array and therefore you get a random-access range, or you 
just use the generic Container and get an input range.

> I see a range as being useful for iteration or algorithms, but not for 
> general use.  A great example is AAs.  Would you say that an AA *is* a 
> range or should *provide* a range?  If it is a range, does that mean you 
> remove elements as you call popFront?  Does that make any sense?  If it 
> doesn't, then what happens if you add elements through another alias to 
> that AA?

An AA provides several ranges - among which byKey and byValue.


Andrei



More information about the Digitalmars-d mailing list