output ranges: by ref or by value?

Steven Schveighoffer schveiguy at yahoo.com
Sat Jan 2 18:46:57 PST 2010


On Sat, 02 Jan 2010 19:51:41 -0500, Andrei Alexandrescu  
<SeeWebsiteForEmail at erdani.org> wrote:

> Steven Schveighoffer wrote:
>>  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).

Yes, that's what I meant :)  I haven't gotten used to struct constructors.

> 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.

That was the point of the enum.  It would be a synthetic difference, but  
IMO so is save.  If it turns out that the only usable times you implement  
save all look like:

auto save() { return *this; }

then save gives you nothing.  It's kind of a proof by induction (I think).

You say that algorithm A requires the ability to save the state of a  
range.  So I define a function save on a range which cannot be saved by a  
simple assignment (i.e. a file).  I use A on that range, and the results  
are not what I expect or kill performance or consume unneeded resources,  
I'd rather not use algorithm A on that range, negating the need to define  
a save function in the first place.

I think that's what we're going to end up with.

> 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.

I meant what actual types of ranges, not categories, I don't know how to  
say this better...  i.e. the file range is one type, what are others?   
What I'm looking for is ranges that would otherwise be input ranges unless  
you define save on them.  For example, a file range without save is an  
input range, cannot be a forward range.  But if you define save, it is a  
forward range.  An array is an example of a range that can be a forward  
range without the save function.

My theory is, given this list of ranges, if you pair them with an  
algorithm that requires save capability, you wouldn't want to use that  
algorithm on it anyways (kinda like the consume example).

> 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.)

The range interface is compile-time only, there is no need to define it in  
the container interface.  opSlice does not need to be part of the  
interface, even if it is defined on all the container types.

opApply makes for a much better interface-friendly iteration anyways.

BTW, the primitives in dcollections are:

clear(); // clear all elements
remove(V v); // remove an element
contains(V v); // returns whether an element is contained in the colleciton
length(); // the length of the collection
dup(); // duplicate the collection
opApply(int delegate(ref V v) dg); // iterate the collection
opApply(int delegate(ref bool doPurge, ref V v) dg); // purge the  
collection

That means it covers only empty in your list of must-have functions (via  
length() == 0).  Add is not a primitive because the Map collections  
shouldn't assign some random key to the new element.  removeAny is defined  
only on sets and multisets, but I'm not sure that it couldn't be moved to  
Collection, in fact, I'll probably do that.

Note that it's missing begin and end which are defined on every single  
container type (i.e. the equivalent of the all-elements range).  This is  
because those primitives return a struct that is different for every  
container type.

It also surpasses opSlice via opApply, since all an input range can do  
anyways is iterate.  In fact, opApply is more powerful because you can  
change elements and remove elements (via purging).  Plus it's more  
efficient than a range-via-interface.

>> 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.

I misunderstood your statement "[a container hierarchy] does need range  
interfaces."  I thought you meant containers themselves need to implement  
the range interface, I see now that isn't the case, so my bad.

-Steve



More information about the Digitalmars-d mailing list