April 12, 2014
Am 12.04.2014 02:19, schrieb Walter Bright:
> On 4/11/2014 12:30 PM, Paulo Pinto wrote:
>> Sure, but aren't those use cases a consequence of C's misuse of enums,
>> which are
>> handled better by numeric constants?
>
> Again, a list of numeric constants offers no benefit of having a
> separate type.
>

You are right, but a type that can hold values that aren't part of the sequence is also not optimal.
April 12, 2014
On Saturday, 12 April 2014 at 03:07:39 UTC, Steven Schveighoffer wrote:
> I don't really understand this statement. The problem I'm trying to solve is that final switch doesn't do what it's supposed to do.
>

final switch is a fine construct. The fact that it can be fed garbage is the problem.

It can be fed garbage because enum to not provide any guarantee whatsoever. You are trying to solve a political problem because it appear that Walter is not gonna admit that he conflated 2 semantic in one thing, and achieve to make it useless for both.

> final switch is fundamentally broken. It is supposed to be an input condition to final switch that the value MUST be one of the enum values. By allowing arbitrary math on enums, this is not the case, even in @safe code.
>

You are arguing that final switch is broken, but your argument show that enums fail to provide any guarantee. You are avoiding the logical conclusion.

> In any case, it doesn't solve all enum problems, just that one. Will it break code? Code that is written correctly, it will break trivially (just add final to the enum declaration). Code that is not written correctly, you will either have to use normal switch, or fix the code.
>

The most basic, fundamental building block that is needed is a type with retained values. Literally every single other use case presented here can be built on top of this as library.

Additionally, this is also the only use case where compiler can take advantage of the knowledge it has for other things (optimization purpose, or additional feature like final switch).

There is no point in introducing additional core language feature, simply sanitize what exists and extends library support for uses cases discussed in this thread.
April 12, 2014
On Saturday, April 12, 2014 07:25:34 deadalnix wrote:
> There is no point in introducing additional core language feature, simply sanitize what exists and extends library support for uses cases discussed in this thread.

Simply making it so that any operation (other than casting) on a variable of an enum type which isn't guaranteed to result in one of that enum's enumerated values results in the base type rather than the enum type would fix the problem. final switch would work great at that point. The other use cases could then easily be done via aliases or structs or some other library solution. But as it stands, enums are not protected well enough to guarantee that they're not going to have invalid values (thereby screwing up stuff like final switch), and the little protection that they do provide (you can't initialize them or assign to them non-enumerated values) just gets in the way of the other use cases. What we have is stuck halfway in between, which is just plain bad IMHO.

We should either fix it so that enum types are protected by the compiler against having non-enumerated values, or remove the restrictions on them and just treat enum types as aliases. What we have now doesn't properly serve any of the use cases.

- Jonathan M Davis
April 12, 2014
On Friday, April 11, 2014 19:52:58 Andrei Alexandrescu wrote:
> On 4/11/14, 6:59 PM, Jonathan M Davis wrote:
> > On Friday, April 11, 2014 17:01:15 Andrei Alexandrescu wrote:
> >> On 4/11/14, 12:31 PM, Jonathan M Davis wrote:
> >>> On Friday, April 11, 2014 08:22:01 Steven Schveighoffer wrote:
> >>>> final enum name { ... }
> >>> 
> >>> This only makes sense to me if it's then illegal to declare any
> >>> variables
> >>> of that enum type, because if you're looking to use an enum to give a
> >>> list of possible values rather than enumerating the exact list of
> >>> values,
> >>> why would you be marking _any_ variable as being of that enum type?
> >> 
> >> I don't understand that contention. "final" clarifies that the set of values in the enumeration is closed.
> > 
> > I take issue with the idea that it makes sense to have any variables of an enum type that isn't one of the values enumerated in the enum.
> 
> I explained the possible situations in the original post of this thread. E.g. one may be unable to enumerate all user IDs, but may be compelled to enumerate a few remarkable ones and ascribe user IDs a separate type. -- Andrei

And what would the purpose by of giving them their own type? If the enum doesn't enumerate all of the values, then you're clearly going to be using other values with it, meaning that any restrictions on setting a variable to those other values is going to be a problem (as is currently the case with enums - assignment and initialization are protected against non-enumerated values). You'd just end up with a enum type which is effectively an alias for the enum's base type except that assigning it non-enumerated values requires casting, and you can overload a function on the enumerated values vs the non- enumerated ones (or at  least, non-enumerated ones which aren't cast to the enum type) by overloading a function on the enum type and its base type - which would be highly bug-prone IMHO.

In such a case, the enum type is adding nothing but a name, so why not just use an alias - especially when even the minimal protections that enum types currently have against other values will get in the way of using it for non- enumerated values. And that would be particularly painful in your example, because the enum only had UserID.nobody and UserID.expired, meaning that nearly ever time you initialized or assigned to a UserID, you'd have to cast. It sounds like what you really want/need is an alias along with an enum with the constants for nobody and expired, but given how enums work even now (without the full protections that I think they should have), actually using the enum type for user IDs is just going to cause problems.

- Jonathan M Davis
April 12, 2014
On Saturday, April 12, 2014 05:59:29 Jonathan M Davis wrote:
> On Friday, April 11, 2014 19:52:58 Andrei Alexandrescu wrote:
> > On 4/11/14, 6:59 PM, Jonathan M Davis wrote:
> > > I take issue with the idea that it makes sense to have any variables of
> > > an
> > > enum type that isn't one of the values enumerated in the enum.
> > 
> > I explained the possible situations in the original post of this thread. E.g. one may be unable to enumerate all user IDs, but may be compelled to enumerate a few remarkable ones and ascribe user IDs a separate type. -- Andrei
> 
> And what would the purpose by of giving them their own type? If the enum doesn't enumerate all of the values, then you're clearly going to be using other values with it, meaning that any restrictions on setting a variable to those other values is going to be a problem (as is currently the case with enums - assignment and initialization are protected against non-enumerated values). You'd just end up with a enum type which is effectively an alias for the enum's base type except that assigning it non-enumerated values requires casting, and you can overload a function on the enumerated values vs the non- enumerated ones (or at  least, non-enumerated ones which aren't cast to the enum type) by overloading a function on the enum type and its base type - which would be highly bug-prone IMHO.
> 
> In such a case, the enum type is adding nothing but a name, so why not just use an alias - especially when even the minimal protections that enum types currently have against other values will get in the way of using it for non- enumerated values. And that would be particularly painful in your example, because the enum only had UserID.nobody and UserID.expired, meaning that nearly ever time you initialized or assigned to a UserID, you'd have to cast. It sounds like what you really want/need is an alias along with an enum with the constants for nobody and expired, but given how enums work even now (without the full protections that I think they should have), actually using the enum type for user IDs is just going to cause problems.

On further reflection, I think that having enum and final enum would make sense as long as enum were changed so that it introduced an alias rather than a new type. A new type just doesn't make sense if you're not listing all the values - especially because the few protections that the compiler has against giving an enum a non-enumerated value just get in the way of that use case - whereas if the enum _is_ intended to enumerate all its values, it makes sense that it would introduce a new type that was protected against having it set to non-enumerated values. So,

enum UserID : ulong { nobody, expired = ulong.max }

would then introduce an alias UserID for ulong as well as namespace the constants nobody and expired, but there would be no new type introduced, so it would not protect against being assigned non-enumerated values, and it would not affect overloading. It also wouldn't work with final switch.

final enum State { initial, waiting, running, done }

would then be the same as

enum State { initial, waiting, running, done }

is now except that the compiler would actually guarantee that no variable of type State could have a non-enumerated value unless a cast was used (so any operation which wasn't guaranteed to result in an enumerated value would result in a value of the base type rather than the enum type). And of course, it would then work properly with final switch, because it couldn't have a non- enumerated value unless you forced it with a cast.

With that separation, we can then separate out the enums which are really enumerations and those which are really just a group of related constants. But I think that the key thing is that introducing a new type really doesn't make sense if it's just a group of related constants. Introducing an alias is fine - but a new type just doesn't make sense IMHO.

For better or worse, if we went this route, we'd end up with something closer to C++11's enums and enum classes, though unlike enum classes, our final enum would implicitly convert to its base type, and operations on it which weren't guaranteed to result in a valid enum value would result in the base type, whereas it's my understanding that you have to overload operators to get any of that working at all with enum classes.

- Jonathan M Davis
April 12, 2014
On Saturday, 12 April 2014 at 13:33:07 UTC, Jonathan M Davis wrote:
> On further reflection, I think that having enum and final enum would make
> sense as long as enum were changed so that it introduced an alias rather than
> a new type. A new type just doesn't make sense if you're not listing all the
> values - especially because the few protections that the compiler has against
> giving an enum a non-enumerated value just get in the way of that use case -
> whereas if the enum _is_ intended to enumerate all its values, it makes sense
> that it would introduce a new type that was protected against having it set to
> non-enumerated values. So,
>
> enum UserID : ulong { nobody, expired = ulong.max }
>
> would then introduce an alias UserID for ulong as well as namespace the
> constants nobody and expired, but there would be no new type introduced, so it
> would not protect against being assigned non-enumerated values, and it would
> not affect overloading. It also wouldn't work with final switch.
>
> final enum State { initial, waiting, running, done }
>
> would then be the same as
>
> enum State { initial, waiting, running, done }
>
> is now except that the compiler would actually guarantee that no variable of
> type State could have a non-enumerated value unless a cast was used (so any
> operation which wasn't guaranteed to result in an enumerated value would
> result in a value of the base type rather than the enum type). And of course,
> it would then work properly with final switch, because it couldn't have a non-
> enumerated value unless you forced it with a cast.
>
> With that separation, we can then separate out the enums which are really
> enumerations and those which are really just a group of related constants. But
> I think that the key thing is that introducing a new type really doesn't make
> sense if it's just a group of related constants. Introducing an alias is fine
> - but a new type just doesn't make sense IMHO.
>
> For better or worse, if we went this route, we'd end up with something closer
> to C++11's enums and enum classes, though unlike enum classes, our final enum
> would implicitly convert to its base type, and operations on it which weren't
> guaranteed to result in a valid enum value would result in the base type,
> whereas it's my understanding that you have to overload operators to get any
> of that working at all with enum classes.
>
> - Jonathan M Davis

If we were really serious about changing enums, I'd say scrap the whole thing and take a look at how Rust does them, and change the semantics of final switch to allow for pattern matching, while leaving switch around as a legacy C construct and discouraging its use in regular code.
April 12, 2014
On 4/12/2014 5:59 AM, Jonathan M Davis wrote:
> And what would the purpose by of giving them their own type?

We are *really* going around in circles here.

April 12, 2014
On 4/12/14, 5:59 AM, Jonathan M Davis wrote:
> On Friday, April 11, 2014 19:52:58 Andrei Alexandrescu wrote:
>> On 4/11/14, 6:59 PM, Jonathan M Davis wrote:
>>> On Friday, April 11, 2014 17:01:15 Andrei Alexandrescu wrote:
>>>> On 4/11/14, 12:31 PM, Jonathan M Davis wrote:
>>>>> On Friday, April 11, 2014 08:22:01 Steven Schveighoffer wrote:
>>>>>> final enum name { ... }
>>>>>
>>>>> This only makes sense to me if it's then illegal to declare any
>>>>> variables
>>>>> of that enum type, because if you're looking to use an enum to give a
>>>>> list of possible values rather than enumerating the exact list of
>>>>> values,
>>>>> why would you be marking _any_ variable as being of that enum type?
>>>>
>>>> I don't understand that contention. "final" clarifies that the set of
>>>> values in the enumeration is closed.
>>>
>>> I take issue with the idea that it makes sense to have any variables of an
>>> enum type that isn't one of the values enumerated in the enum.
>>
>> I explained the possible situations in the original post of this thread.
>> E.g. one may be unable to enumerate all user IDs, but may be compelled
>> to enumerate a few remarkable ones and ascribe user IDs a separate type.
>> -- Andrei
>
> And what would the purpose by of giving them their own type?

Less error proneness.

> If the enum
> doesn't enumerate all of the values, then you're clearly going to be using
> other values with it, meaning that any restrictions on setting a variable to
> those other values is going to be a problem (as is currently the case with
> enums - assignment and initialization are protected against non-enumerated
> values). You'd just end up with a enum type which is effectively an alias for
> the enum's base type except that assigning it non-enumerated values requires
> casting, and you can overload a function on the enumerated values vs the non-
> enumerated ones (or at  least, non-enumerated ones which aren't cast to the
> enum type) by overloading a function on the enum type and its base type -
> which would be highly bug-prone IMHO.

Sorry, your speculations are mistaken. The pattern works well and we've been using it repeatedly and with good results since C++ introduced "enum class".


Andrei


April 12, 2014
 Oh you D guys, arguing about enums for 187 posts! Find something more interesting to talk about, this is boring:(

 The only thing about enum class that pisses me off is not being able to index into an array by default.



April 13, 2014
On Saturday, 12 April 2014 at 19:43:30 UTC, Andrei Alexandrescu wrote:
> Sorry, your speculations are mistaken. The pattern works well and we've been using it repeatedly and with good results since C++ introduced "enum class".
>

Can you provide a sample code so we understand what you are talking about ? It do not seems to me that enum classes defeat Jonathan point, but I may be wrong.