We are arguing in circles, so I will just stop :)
I'll address the one point I think we both feel is most important below
On Sun, 03 Jan 2010 17:19:52 -0500, Andrei Alexandrescu
<[email protected]> wrote:
Steven Schveighoffer wrote:
On Sun, 03 Jan 2010 09:25:25 -0500, Andrei Alexandrescu
<[email protected]> wrote:
Steven Schveighoffer wrote:
Not having opSlice be part of the interface itself does not preclude
it from implementing opSlice, and does not preclude using ranges of
it in std.algorithm. If I'm not mistaken, all functions in
std.algorithm rely on compile time interfaces. opApply allows for
full input range functionality for things like copying and outputting
where templates may not be desired.
The point is how much container-independent code can someone write by
using the Container interface. If all you have is a Container, you
can't use it with any range algorithm.
The answer is, you don't. Ranges via interfaces are slower than
primitive functions defined on the interface, so use what's best for
interfaces when you have an interface and what's best for compile-time
optimization when you have the full implementation.
I understand we don't agree on this. To me, making containers work with
algorithms is a drop-dead requirement so I will rest my case.
Nevertheless, I think there's one point that got missed. It's a tad
subtle, and I find it pretty cool because it's the first time I used
covariant returns for something interesting. Consider:
interface InputIter(T) { ... }
interface ForwardIter(T) : InputIter!T { ... }
class Container(T) {
InputIter!T opSlice() { ... }
}
class Array(T) : Container(T) {
class Iterator : ForwardIter!T {
... all final functions ...
}
Iterator opSlice();
}
Now there are two use cases:
a) The user uses Array specifically.
auto a = new Array!int;
sort(a[]);
In this case, sort gets a range of type Array!(int).Iterator, which
defines final primitives. Therefore, the compiler does not emit ONE
virtual call throughout. I believe this point was lost.
b) The user wants generality and OOP-style so uses Container throughout:
Container!int a = new Array!int;
auto found = find(a[], 5);
This time find gets an InputIter!int, so it will use virtual calls for
iteration.
The beauty of this design is that without any code duplication it
clearly spans the spectrum between static knowledge and dynamic
flexibility - within one design and one implementation. This is the
design I want to pursue.
I see why it is attractive to you. But to me, algorithms are not the main
driver for containers. One thing we both agree on -- when you know the
full implementation, algorithms from std.algorithm should be implemented
as fast as possible. Where we disagree is what is desired when the full
implementation is not known. I contend that the ability to link with
std.algorithm isn't a requirement in that case, and is not worth
complicating the whole world of ranges to do so (i.e. build std.algorithm
just in case you have reference-type ranges with a "save" requirement).
If you don't know the implementation, don't depend on std.algorithm to
have all the answers, depend on the implementation which is abstracted
correctly by the interface.
What this means is there will be some overlap in functions that are
defined on the interfaces and functions defined in std.algorithm. Most
likely the overlapping functions both point to the same implementation
(naturally, this should live in std.algorithm). This is for convenience
to people who want to use containers in the interface form, so they do not
need to concern themselves with the abilities of std.algorithm, they just
want containers that help them get work done.
There is still one flaw in your ideas for covariant types: even though
final functions can be used throughout and the possibility for inlining
exists, you *still* need to use the heap to make copies of ranges. That
was and still is my biggest concern.
I think the rest of this whole post is based on our disagreement of these
design choices, and it really doesn't seem productive to continue all the
subtle points.
[rest of growing disagreement snipped]
BTW, I use covariant return types freely in dcollections and I agree it
kicks ass. It seems like black magic especially when you are returning
possibly a class or interface which need to be handled differently.
-Steve