May 22, 2010
Hello Vladimir,

> On Thu, 20 May 2010 04:42:35 +0300, Steven Schveighoffer
> <schveiguy@yahoo.com> wrote:
> 
>> interfaces
>> 
> Does that imply that the most important methods are virtual?
> 
> If so, say good-bye to inlining, and hello to an additional level of
> dereferencing.
> 

From a technical standpoint there is no reason that a method needs to be called virtually from a class reference just because the same method gets called virtually from an interface reference.

-- 
... <IXOYE><



May 22, 2010
Hello Andrei,

> On 05/20/2010 08:22 AM, Steven Schveighoffer wrote:
> 
>> Michel Fortin Wrote:
>> 
>>> On 2010-05-20 06:34:42 -0400, Steven
>>> Schveighoffer<schveiguy@yahoo.com>  said:
>>>> I understand these points, but I'm already using interfaces to copy
>>>> data between containers.  I don't have to, I could have used
>>>> generic code, but this way, only one function is instantiated to
>>>> copy data from all the other containers.  The problem with using
>>>> generic code is that the compiler will needlessly duplicate
>>>> functions that are identical.
>>>> 
>>> One question. Have you calculated the speed difference between using
>>> an interface and using generic code? Surely going through all those
>>> virtual calls slows things down a lot.
>>> 
>>> I do like interfaces in principle, but I fear it'll make things much
>>> slower when people implement things in term of interfaces. That's
>>> why I'm not sure it's a good idea to offer container interfaces in
>>> the standard library.
>>> 
>> It's not that much slower.  You get a much higher speedup when things
>> can be inlined than virtual vs. non-virtual.
>> 
>> However, I should probably make all the functions in the concrete
>> implementations final.  I made several of them final, but I should do
>> it across the board.
>> 
> Yup. Even Java does that. I forgot whether it was a recanting of a
> former stance (as was the case with synchronized) or things were like
> that from the get-go.
> 
>> One thing I just thought of -- in dcollections, similar types can be
>> compared to one another.  For example, you can check to see if a
>> HashSet is equal to a TreeSet.  But that would not be possible
>> without interfaces.
>> 
> Of course it would be possible. You write a generic function that
> takes two generic container and constrain the inputs such that at
> least one has element lookup capability. (Complexity-oriented design
> for the win!)
> 
> Andrei
> 

Cool. Now how do I write code so that it will always iterate the collection with the bigger O() lookup time (O(n) before O(log2(n)) before O(log16(n)) before O(1))? :D

-- 
... <IXOYE><



May 22, 2010
Andrei Alexandrescu Wrote:
> 
> I don't know Tango, but Java's containers are a terrible example to follow. Java's container library is a ill-advised design on top of an underpowered language, patched later with some half-understood seeming of genericity. I think Java containers are a huge disservice to the programming community because they foster bad design.

Tango's container library is or was a port of Doug Lea's containers for Java.  While Doug Lea is an absolute master with concurrency, I never liked his container library very much.  As you've said, the common abstractions it draws are weird and not terribly useful.

> I need to disagree with that. I've done and I do a ton of binary interoperability stuff. You never expose a generic container interface! Interoperable objects always embody high-level logic that is specific to the application. They might use containers inside, but they invariably expose high-level, application-specific functionality.

This.  Iterators (or ranges) are passed all over the place, but when operating directly on a container I always want to know what kind of container I'm dealing with.  Cost of operations is an issue, I may need to manually sort at some point or be aware that iterators will be invalidated, etc.

Steve has already said he doesn't use the interfaces, and I'm a huge fan of not doing speculative design.  It's invariably wrong and then you get stuck supporting it.  I'd vote to drop the interfaces.  They can always be added back later anyway.
May 22, 2010
Walter Bright wrote:
> If we can get anywhere close to that level of success with ranges and containers, we should all be well pleased.

Mike Taylor has a phrase for that I think is well-coined: "impedance matching", defined as the work necessary to get one library module to work with another library module.

One example of bad impedance matching is C++ iostreams' attempt to make a memory buffer look like a file. Phobos propagated that mistake in its own streams. A better way is to use ranges, where the file is accessed via a range and a memory buffer is accessed via a range. Then anything that operates on data is written to the range, not a fake file interface.
May 22, 2010
On 2010-05-21 22:55:16 -0400, Walter Bright <newshound1@digitalmars.com> said:

> Walter Bright wrote:
>> If we can get anywhere close to that level of success with ranges and containers, we should all be well pleased.
> 
> Mike Taylor has a phrase for that I think is well-coined: "impedance matching",
> defined as the work necessary to get one library module to work with another
> library module.

This makes me think about something.

In principle, I like the idea of containers being reference type. It works well when passing a container to functions. But at the same time, I despite it. By-reference containers forces you to have extra indirections even when you don't need them, and you have to worry about null. Sometime a value-type would be better, when creating more complex data structures for instance:

	class Channel {
		private {
			Array!Message inbound;
			Array!Message outbound;
		}

		...
	}

What's the point of having extra indirection here?

I've been thinking about having both by-value and by-reference containers. The first-class name would be given to the by-reference container to give it more visibility, but that by-reference container would be a simple wrapper for a by-value container "part" implemented in a struct:

	struct ArrayPart(T) { ... } // by value array container.

	class Array(T) {
		ArrayPart!T part;
		alias part this;
	}

So now, if you want to reuse a container "part" to build some kind of more complex data structure, you can do it like this:

	class Channel {
		private {
			ArrayPart!Message inbound;
			ArrayPart!Message outbound;
		}

		...
	}

No silly extra indirection.

That said, all this gives me a feeling of an overcomplicated design. After all, the problem is that you want to pass the container by reference in function arguments, but it's __too easy to forget the ref__. Perhaps that's the problem that should be fixed.

Couldn't we just make a struct that cannot be implicitly copied? Perhaps something like this:

	@explicitdup struct Array {  }

	void testVal(Array array);
	void testRef(ref Array array);

	unittest {
		Array array;
		testVal(array);     // error, cannot copy array implicitly
		testVal(array.dup); // ok, array is copied
		testRef(array);     // ok, array is passed by reference
	}

If there's already a way to achieve this, I couldn't find it.

-- 
Michel Fortin
michel.fortin@michelf.com
http://michelf.com/

May 22, 2010
Michel Fortin:
> and you have to worry about null.

Nonnull references of course solve this problem.


> Couldn't we just make a struct that cannot be implicitly copied?

The @disable attribute was invented for this purpose too:

struct Foo {
    int x;
    @disable this(this) {}
}
void main() {
    Foo f1;
    Foo f2 = f1;
}


That prints:
test.d(7): Error: struct test9.Foo is not copyable because it is annotated with @disable

Bye,
bearophile
May 22, 2010
On 2010-05-22 07:56:31 -0400, Michel Fortin <michel.fortin@michelf.com> said:

> 	@explicitdup struct Array {  }
> 
> 	void testVal(Array array);
> 	void testRef(ref Array array);
> 
> 	unittest {
> 		Array array;
> 		testVal(array);     // error, cannot copy array implicitly
> 		testVal(array.dup); // ok, array is copied
> 		testRef(array);     // ok, array is passed by reference
> 	}
> 
> If there's already a way to achieve this, I couldn't find it.

Apparently it's already achievable this way:

	struct Array {
		@disable this(this);
		Array dup() { return Array(...); }
		...
	}

It also blocks simple assignments:

	Array array2 = array;     // error, array is not copyable
	Array array3 = array.dup; // ok

With this, I don't think we need containers to be reference types anymore. The naive error of copying containers everywhere without you knowing about it (as it occurs in C++) is simply no longer possible.

-- 
Michel Fortin
michel.fortin@michelf.com
http://michelf.com/

May 22, 2010
On 2010-05-22 08:16:27 -0400, bearophile <bearophileHUGS@lycos.com> said:

> Michel Fortin:
>> Couldn't we just make a struct that cannot be implicitly copied?
> 
> The @disable attribute was invented for this purpose too:
> 
> struct Foo {
>     int x;
>     @disable this(this) {}
> }
> void main() {
>     Foo f1;
>     Foo f2 = f1;
> }
> 
> 
> That prints:
> test.d(7): Error: struct test9.Foo is not copyable because it is annotated with @disable

Indeed, thanks. I figured it out by myself while you were writing this.


-- 
Michel Fortin
michel.fortin@michelf.com
http://michelf.com/

May 22, 2010
BCS <none@anon.com> wrote:

> Cool. Now how do I write code so that it will always iterate the collection with the bigger O() lookup time (O(n) before O(log2(n)) before O(log16(n)) before O(1))? :D

Add a function.

auto foo( R1, R2 )( R1 r1, R2 r2 ) if ( R1.complexity( 10_000 ) > R2.complexity( 10_000 ) ) {
	...
}

auto foo( R1, R2 )( R2 r1, R1 r2 ) if ( R1.complexity( 10_000 ) < R2.complexity( 10_000 ) ) {
	...
}

-- 
Simen
May 22, 2010
Michel Fortin wrote:
> On 2010-05-21 22:55:16 -0400, Walter Bright <newshound1@digitalmars.com> said:
> 
>> Walter Bright wrote:
>>> If we can get anywhere close to that level of success with ranges and containers, we should all be well pleased.
>>
>> Mike Taylor has a phrase for that I think is well-coined: "impedance matching",
>> defined as the work necessary to get one library module to work with another
>> library module.
> 
> This makes me think about something.
> 
> In principle, I like the idea of containers being reference type. It works well when passing a container to functions. But at the same time, I despite it. By-reference containers forces you to have extra indirections even when you don't need them, and you have to worry about null. Sometime a value-type would be better, when creating more complex data structures for instance:
> 
>     class Channel {
>         private {
>             Array!Message inbound;
>             Array!Message outbound;
>         }
> 
>         ...
>     }
> 
> What's the point of having extra indirection here?

Good question. I think the answer is:

1. When do you ever want to copy a collection? I almost never do, because copying one is an inherently expensive operation.

2. When you copy a collection, do you copy the container or the elements in the container or both? One would have to carefully read the documentation to see which it is. That increases substantially the "cognitive load" of using them.

3. Going to lengths to make them value types, but then disabling copying them because you want people to use them as reference types, seems like a failure of design somewhere.

4. That silly extra level of indirection actually isn't there. Consider that even value locals are accessed via indirection: offset[ESP]. For a reference collection we have: offset[EBX]. No difference (yes, EBX has to be loaded, but if it is done more than once it gets cached in a register by the compiler).

5. Just making them all reference types means the documentation and use become a lot simpler.