April 11, 2014
On Friday, April 11, 2014 11:32:36 Walter Bright wrote:
> On 4/11/2014 4:18 AM, Jonathan M Davis wrote:
> > I don't see much point to enums if they're not intended to list all of their values.
> 
> Again, bit masks, Color, etc., and to provide a simple integral type that behaves like an integral type yet can be overloaded and type checked.
> 
> Andrei has pointed out that these uses are unsound if you desire that the enumeration lists all possible values, and he's right. But I don't think that automatically makes them pointless.

IMHO, it's pointless to then declare variables of that enum type. Being able to have something like

enum Color : uint { red = 0xFF0000, green = 0x00FF00, blue = 0x0000FF }

is useful for listing constants, but it then doesn't make any sense IMHO to declare any variables of type Color. It's purpose is to group constants together, not really to declare a new type. If you want functions to have an integral type representing color where not all the values are enumerated, why not just use an alias? The only differences are protecting against initializing or assigning an enum variable to other values (which would just be annoying with other values, not helpful) and function overloading. And it just seems bug-prone to me to try and overload on an enum type when it's just a list of constants, and it's expected that other, unlisted values will be used (especially when one of the other overloads is for the base type). If you miss one cast, it'll call the wrong overload.

So, while I agree that having a listing of associated constants can be useful, I completely disagree that it makes any sense for declaring a list of associated constants to then result in a new type.

- Jonathan M Davis
April 11, 2014
On Friday, 11 April 2014 at 19:53:14 UTC, deadalnix wrote:
> On Friday, 11 April 2014 at 12:21:59 UTC, Steven Schveighoffer wrote:
>> final enum name { ... }
> I unerstand what you are trying to do here. You are solving a political problem, with a solution that is inferior (but won't require anyone to be proven wrong, so that have greater chance to be accepted).
Why is it inferior?
Both kinds of enums are in use, and I can not decide which type is more "obvious" or "most common" (e.g. I like flag-arrays a lot - and in D it's usual to define them as enums so do I).
> The problem is that is increase language complexity
Yes, a new type increases the complexity. But what would be the alternative? Removing enums? No, they are much too useful. But they come in two totaly different kinds, so having two distinct types is very sensible, I think. It is well worth the small increase in complexity (hey, with the "trick" of using final enum we didn't even need a new keyword and the difference is easy to remember).

> plus make the most common use case of enum nit the most direct, obvious one. It is going to break every single piece of code that use final switch.
I think this will be a 2-step implementation. First one should get a warning that non-final enum will be deprecated from use in final switch and second the warning will be turned into an error.
In fact I think its a good idea to review every such warning if really final enum should be used or if it would be better to use non-final switch.

> And the default behavior of enum still remain a useless 3 headed monster,
Why? It should now only be used for incomplete enumerations and can no more be mis-used in final switches, so one head is off. About the remaining two: if you like to use arithmetics on your enums is up to you. If you don't like it, don't use it.
I use flag-arrays often and wouldn't like to define a complicated structure for each of them.
April 11, 2014
On Friday, 11 April 2014 at 05:52:38 UTC, Walter Bright wrote:
>
> Bit flag usage is often more complex than just bit flags. Sometimes there are "unions" of different meanings for some bit sequences depending on the state of other bits.
>
> Besides, bit flags aren't the only instance of "an integral type where some values have names". The Color enum is another one.

But they are not integral types.  They are only modeled that way because the language doesn't have anything better.

** NOTE **
The following is not a proposal; it is admittedly naive and very incomplete. I'm just trying to provoke some thought.  The point being that enum is too often used for things it isn't well suited for because the language lacks a more correct means.  Provide a more correct means, and users will migrate away from enum and the usage of enum becomes more well defined or potentially even deprecated.

**********************************
* bitflags
**********************************
Consider...
enum Policy
{
  read,
  write,
  readwrite
}

What is the value of 'read'?  I don't mean the value the compiler assigned to it, I mean the intrinsic value of 'read'.  And what is readwrite?  Is it 'read + write', 'read ~ write', 'read | write'?

All of these questions are nonsense because 'read', 'write', and 'readwrite' have no value.  They are just given an arbitrary value by the compiler so they can be distinguished from one another.

'Policy' is really an enumeration of sets, and I mean the 'set theory' kind of sets.  We have the read set, the write set, and the union of the read set and the write set, aka the readwrite set.  What's needed is a way to tell the compiler that we want a set of bits and not integral values, so it can enforce appropriate usage.

Maybe something along these lines:
bitflags!(2) Policy
{
    read = 1,
    write = 2,
    readwrite = read | write,
}

Because we told the compiler that the type is a bitflags, and not just some arbitrary integral values, it can enforce certain rules about its usage.

1.  Arithmetic (+, -, *, /) is invalid.  Policy.read + Policy.write is nonsense.
2.  Comparison operations (<, >, <=, >=) are invalid.  Again the value is arbitrary and meaningless.
2.  Set operations (|, &, ^, ~) are valid using the bitwise operators as substitues for ∪,∩, etc... Policy.read | Policy.write creates a new set that is equal to the readwrite set.
4.  Equality operators (==, !=) are valid.
5.  If you want to expose the value in memory and use it as an integral value, you must cast it to the integral value.
6.  If you want to use an integral value as a bitflags, you must cast it to bitflags

**********************************
* ordinal
**********************************
From wikipedia (http://en.wikipedia.org/wiki/Ordinal_data): In statistics, ordinal data is a statistical data type consisting of numerical scores that exist on an ordinal scale, i.e. an arbitrary numerical scale where the exact numerical quantity of a particular value has no significance beyond its ability to establish a ranking over a set of data points.

Again, enum isn't well suited for this because it's too vague. Something more specific is needed.

//Just so you know ARM Cotex-M model's interrupt priority this way.
ordinal Priority : ubyte
{
    Low = 2,
    Medium = 1,
    High = 0
}

Here, again the value is arbitrary.  We told the compiler it's an ordinal, so the ranking is actually the lexical order in which they appear (low to high). So High > Low == true despite the arbitrary value assigned by the user/compiler. The value only serves as a way to distinguish one from the other in memory.

1.  Comparison operations (<, >, <=, >=) are valid in order to compare the ranking.  Comparison is on lexical order.  Comparing with integral type is invalid without a cast.
2.  Equality operators (==, !=) are valid.
3.  Could potentially use ++ and -- to increase or decrease the ranking.
4.  If you want to expose the value in memory and use it as an integral value, you must cast it to the integral value.
5.  If you want to use an integral value as a Policy, you must cast it to Policy, and if there is no valid policy with that value, it throws an exception.

If I've articulated the general principle well enough, I'm sure the language designers could run with this and design something quite elegant with high utility.
April 11, 2014
On 4/11/14, 11:32 AM, Walter Bright wrote:
> On 4/11/2014 4:18 AM, Jonathan M Davis wrote:
>> I don't see much point to enums if they're not intended to list
>> all of their values.
>
> Again, bit masks, Color, etc., and to provide a simple integral type
> that behaves like an integral type yet can be overloaded and type checked.

The point here is to not hurt most for the benefit of the few. I think insisting on these use cases is missing that point.

> Andrei has pointed out that these uses are unsound if you desire that
> the enumeration lists all possible values, and he's right. But I don't
> think that automatically makes them pointless.

Soundness is a Big Deal(tm). It's also a false choice that assumes no correct design is possible. The fact that we're willing to trade soundness for... for what? For saving us the small effort of designing the feature right? That's very un-D-y.

> Heck, look at the "StorageClass" typedef in dmd's source code, and the
> list of STC macro definitions. That would make a nice D enum, and have
> some type safety too. It would be much more sound than the C method used.

That's but one anecdote. There are plenty of enums (e.g. tokens) in dmd alone that are not flags.

Again: I'm fine with "it's not essential and urgh we kinda botched final switch so we'll go with what we have although it's kind of a bummer"... but claiming it's actually good, that I take issue with.


Andrei

April 12, 2014
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.

Andrei
April 12, 2014
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.

April 12, 2014
On 4/11/2014 12:41 PM, Jonathan M Davis wrote:
> but it then doesn't make any sense IMHO to
> declare any variables of type Color.

Obviously, we are going around in pointless circles now.
April 12, 2014
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. If you want to only enumerate a portion of the possible values, then that would mean that you're declaring a set of related constants rather than the set of possible values for a new type. They're just names for values of an existing type which can have other values, and as such, it makes no sense for those constants to introduce a new type. They're just constants put in a separate namespace, not an enumeration.

So, basically, I'm arguing that it makes no sense to ever have an enum variable of an enum type that is not intended to enumerate _all_ of its possible values. So, if you want to use an enum to group constants together, that's fine, but if that's the purpose, then it shouldn't be creating a new type. It should just be scoping the constants so that they're grouped together for access (e.g. Color.red and Color.green instead of the unscoped constants red and green). And that's trivial enough to do with a struct with manifest constants in it (though it's a bit more verbose). You don't need an enum for that.

As such, I think that if we have enum and final enum, then enum shouldn't actually create a new type - just a namespace for a group of constants.

Or we could just say that all enum types enumerate all of their possible values and put the full restrictions in the compiler against setting them or mutating them to any other values, and then if someone uses an enum to list constants rather than intending it to be a true enumeration, then they're fine just so long as they don't try to declare a variable of the enum type, since the enum values will implicitly convert to the base type. So, all of the restrictions on the enum type which are intended to protect it from having other values wouldn't cause any problems for those trying to use the enum to put constants in a common namespace. It would only cause problems if you tried to use a variable of the enum type and give it other values, which I don't think is a problem at all, since they weren't intended to introduce a new type in the first place.

- Jonathan M Davis
April 12, 2014
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
April 12, 2014
On Fri, 11 Apr 2014 15:53:13 -0400, deadalnix <deadalnix@gmail.com> wrote:

> On Friday, 11 April 2014 at 12:21:59 UTC, Steven Schveighoffer wrote:
>> Idea just came to me. What about notifying the compiler which enums can be used in final switches (and perhaps should be more stringent in allowing math operations):
>>
>> final enum name { ... }
>>
>> -Steve
>
> I unerstand what you are trying to do here. You are solving a political problem, with a solution that is inferior (but won't require anyone to be proven wrong, so that have greater chance to be accepted).

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.

> The problem is that is increase language complexity, plus make the most common use case of enum nit the most direct, obvious one. It is going to break every single piece of code that use final switch. And the default behavior of enum still remain a useless 3 headed monster, that could be created as a library solution, and that nobody really want anyway.

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.

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.

-Steve