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

Reply via email to