November 29, 2015
On Saturday, 28 November 2015 at 13:41:10 UTC, Andrei Alexandrescu wrote:
> On 11/28/15 2:26 AM, Jakob Ovrum wrote:
>> Another thing: wouldn't providing a custom allocator require a separate
>> primitive?
>
> All collections will work with IAllocator. -- Andrei

Yes, I assumed as much. So how would this be handled? Is this not a relevant question to the construction issue?

November 29, 2015
On Friday, 27 November 2015 at 20:14:21 UTC, Andrei Alexandrescu wrote:
> There's this oddity of built-in hash tables: a reference to a non-empty hash table can be copied and then both references refer to the same hash table object. However, if the hash table is null, copying the reference won't track the same object later on.
>
> [...]

static opCAll is just confusing IMHO. Factory function please.

Atila
November 29, 2015
On 11/29/15 5:06 AM, Atila Neves wrote:
> On Friday, 27 November 2015 at 20:14:21 UTC, Andrei Alexandrescu wrote:
>> There's this oddity of built-in hash tables: a reference to a
>> non-empty hash table can be copied and then both references refer to
>> the same hash table object. However, if the hash table is null,
>> copying the reference won't track the same object later on.
>>
>> [...]
>
> static opCAll is just confusing IMHO. Factory function please.

Thanks all. I'll go with the factory function. -- Andrei


November 29, 2015
On Sunday, 29 November 2015 at 15:06:49 UTC, Andrei Alexandrescu wrote:
> Thanks all. I'll go with the factory function. -- Andrei

As Timon suggested, I'd encourage you to go for a free factory function named after the container like RedBlackTree does with redBlackTree rather than having a static factory function, since it's less verbose and allows for type inference, though there's no reason why we couldn't have both:

auto c1 = MyCollection!int.make(1, 2, 3);
auto c2 = myCollection!int(1, 2, 3);
auto c3 = myCollection(1, 2, 3);

Regardless, it's better to avoid static opCall.

- Jonathan M Davis
November 30, 2015
On 11/27/15 3:14 PM, Andrei Alexandrescu wrote:
> There's this oddity of built-in hash tables: a reference to a non-empty
> hash table can be copied and then both references refer to the same hash
> table object. However, if the hash table is null, copying the reference
> won't track the same object later on.
>
> Fast-forward to general collections. If we want to support things like
> reference containers, clearly that oddity must be addressed. There are
> two typical approaches:
>
> 1. Factory function:
>
> struct MyCollection(T)
> {
>      static MyCollection make(U...)(auto ref U args);
>      ...
> }
>
> So then client code is:
>
> auto c1 = MyCollection!(int).make(1, 2, 3);
> auto c2 = MyCollection!(int).make();
> auto c3 = c2; // refers to the same collection as c2
>
> 2. The opCall trick:
>
> struct MyCollection(T)
> {
>      static MyCollection opCall(U...)(auto ref U args);
>      ...
> }
>
> with the client code:
>
> auto c1 = MyCollection!(int)(1, 2, 3);
> auto c2 = MyCollection!(int)();
> auto c3 = c2; // refers to the same collection as c2
>
> There's some experience in various libraries with both approaches. Which
> would you prefer?

How do you prevent the AA behavior? In other words, what happens here:

MyCollection!(int) c1;
auto c2 = c1;
c1 ~= 1;

assert(c2.contains(1)); // pass? fail?

BTW, I third Jonathan's and Timon's suggestion -- go with an external factory function. Use IFTI to its fullest!

-Steve
November 30, 2015
On Monday, 30 November 2015 at 16:06:43 UTC, Steven Schveighoffer wrote:
> MyCollection!(int) c1;
> auto c2 = c1;
> c1 ~= 1;
>
> assert(c2.contains(1)); // pass? fail?
>
> BTW, I third Jonathan's and Timon's suggestion -- go with an external factory function. Use IFTI to its fullest!
>
> -Steve

That should throw, because you're using an uninitialised reference (c1). It's the equivalent to:

Class C { .. }

C c1;
C c2 = c1;
c1.foo(); // call via nullptr

Or it needs to pass, but that's probably not worth it.
November 30, 2015
On 11/30/15 11:21 AM, Tobias Pankrath wrote:
> On Monday, 30 November 2015 at 16:06:43 UTC, Steven Schveighoffer wrote:
>> MyCollection!(int) c1;
>> auto c2 = c1;
>> c1 ~= 1;
>>
>> assert(c2.contains(1)); // pass? fail?
>>
>> BTW, I third Jonathan's and Timon's suggestion -- go with an external
>> factory function. Use IFTI to its fullest!
>
> That should throw, because you're using an uninitialised reference (c1).
> It's the equivalent to:
>
> Class C { .. }
>
> C c1;
> C c2 = c1;
> c1.foo(); // call via nullptr
>
> Or it needs to pass, but that's probably not worth it.

It means such a collection won't operate in the same way that associative arrays do.

If that's the case, I'm OK with that.

Technically, a wrapper could be constructed that performed the "lazy creation".

But my point to Andrei was that the functions he suggests don't actually address the "oddity" of copying AAs. Addressing it, if it's done in the way you say, is as simple as not worrying about null pointers.

-Steve
November 30, 2015
On Saturday, 28 November 2015 at 13:39:35 UTC, Andrei Alexandrescu wrote:
> On 11/28/15 1:59 AM, bitwise wrote:
>>
>> Classes/real-ref-types dont act as you're describing, so why should
>> these fake struct wrapper ref things act this way? This will likely
>> achieve the exact opposite of what you're aiming for, by making
>> something that's supposed to act like a reference type have different
>> behaviour from D's built in ref types.
>
> So what would work for you? -- Andrei

Sorry if that response seemed a tad flippant, but I have to be honest...I am completely against this design...to put it mildly.

I have my own containers to use, but on top of the fact that I would prefer something which is collaboratively maintained, I don't want to be forced to deal with, or support these "reference" containers, which will most likely happen if they get added to Phobos.

I'm really not sure where to begin tearing this idea apart. The principal I have a problem with is much more fundamental than this one decision. In general, there is a lot in D that is very hackish.

I understand that you don't want eager copying of containers, but when I way predictability, simplicity, clarity, and flexibility against that concern, there is no way I'm agreeing with you, when you can simply wrap a proper container in a RefCounted(T) or something. A class is a reference type, and a struct is a value type. If a user sees a struct, they should expect a value type which will copy on assign, and if they see a class, they should expect a reference. In D, the differentiation between value and reference types is clearly specified, and D users _should_ be, and should be expected to be, aware of it.

If you really want reference containers, they should be implemented either as value-type structs, or classes that can work with RefCounted(T). Baking the reference count directly into the container is limiting, and buys nothing. I really don't see a problem with GC'ed classes if you really want reference types. It's going to be forever, if ever before you can actually turn off the GC when using Phobos. At least, if it's a class, you can use Scoped(T), or RefCounted(T) on it...assuming RefCounted(T) is fixed up to work with classes at some point, which seems like a better path then baking ref counting into a container implementation.

I'm feeling a bit repetitive at this point, and wondering if I should have responded to this at all, and I'm sure you know exactly what I'm talking about, and that it's a matter of choice at this point, but there you have it.

    Bit

November 30, 2015
On 11/30/2015 12:56 PM, bitwise wrote:
> On Saturday, 28 November 2015 at 13:39:35 UTC, Andrei Alexandrescu wrote:
>> On 11/28/15 1:59 AM, bitwise wrote:
>>>
>>> Classes/real-ref-types dont act as you're describing, so why should
>>> these fake struct wrapper ref things act this way? This will likely
>>> achieve the exact opposite of what you're aiming for, by making
>>> something that's supposed to act like a reference type have different
>>> behaviour from D's built in ref types.
>>
>> So what would work for you? -- Andrei
>
> Sorry if that response seemed a tad flippant, but I have to be
> honest...I am completely against this design...to put it mildly.
>
> I have my own containers to use, but on top of the fact that I would
> prefer something which is collaboratively maintained, I don't want to be
> forced to deal with, or support these "reference" containers, which will
> most likely happen if they get added to Phobos.
>
> I'm really not sure where to begin tearing this idea apart. The
> principal I have a problem with is much more fundamental than this one
> decision. In general, there is a lot in D that is very hackish.
>
> I understand that you don't want eager copying of containers, but when I
> way predictability, simplicity, clarity, and flexibility against that
> concern, there is no way I'm agreeing with you, when you can simply wrap
> a proper container in a RefCounted(T) or something. A class is a
> reference type, and a struct is a value type. If a user sees a struct,
> they should expect a value type which will copy on assign, and if they
> see a class, they should expect a reference. In D, the differentiation
> between value and reference types is clearly specified, and D users
> _should_ be, and should be expected to be, aware of it.
>
> If you really want reference containers, they should be implemented
> either as value-type structs, or classes that can work with
> RefCounted(T). Baking the reference count directly into the container is
> limiting, and buys nothing. I really don't see a problem with GC'ed
> classes if you really want reference types. It's going to be forever, if
> ever before you can actually turn off the GC when using Phobos. At
> least, if it's a class, you can use Scoped(T), or RefCounted(T) on
> it...assuming RefCounted(T) is fixed up to work with classes at some
> point, which seems like a better path then baking ref counting into a
> container implementation.
>
> I'm feeling a bit repetitive at this point, and wondering if I should
> have responded to this at all, and I'm sure you know exactly what I'm
> talking about, and that it's a matter of choice at this point, but there
> you have it.

Thanks, your response is appreciated! Let me make sure I understand. So, in your opinion:

* Value containers plus a way to wrap them with RefCounted is a better solution than containers with built-in reference semantics.

* The design supported by D most naturally is: classes have reference semantics and structs have value semantics.

* Reference semantics for containers seem to work best with GC. Pursuing reference containers with baked-in RC seems nonproductive.

This is all sensible. Here are a couple of follow-up questions and considerations:

* I couldn't integrate this with the rest of your post: "The principal I have a problem with is much more fundamental than this one decision. In general, there is a lot in D that is very hackish." Could you please elaborate?

* The one matter with the value/RefCounted approach is that RefCounted cannot be made @safe. One core design decision I made was to aim for safe containers. I do agree that if safety is off the table, your design would be a very good choice (probably the best I can think of, and I'd start an implementation using it).


Thanks,

Andrei

December 01, 2015
On Monday, 30 November 2015 at 18:18:38 UTC, Andrei Alexandrescu wrote:
> * The one matter with the value/RefCounted approach is that RefCounted cannot be made @safe.

That's just as true for internal refcounting.