Steven Schveighoffer wrote:
On Sat, 02 Jan 2010 13:51:48 -0500, Andrei Alexandrescu
<[email protected]> 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