April 07, 2014
On 4/6/14, 6:49 PM, Walter Bright wrote:
> On 4/6/2014 4:17 PM, Andrei Alexandrescu wrote:
>> On 4/6/14, 10:52 AM, Walter Bright wrote:
>>> I use enums a lot in D. I find they work very satisfactorily. The way
>>> they work was deliberately designed, not a historical accident.
>>
>> Sorry, I think they ought to have been better. -- Andrei
>
> Sorry, yer wrong!

This program compiles and flag free and no cast in sight but fails at runtime. Textbook example of unsound type design.

import std.stdio;

enum A { x = 2, y = 4 }

void main()
{
    A a = A.x | A.y;
    final switch (a)
    {
        case A.x: break;
        case A.y: break;
    }
}

The "|" operator converts back to an A. It shouldn't. In this case it provides a value not only outside the enum range, but even greater than A.max (when converted to integer).

I'm fine with "yes, it's unsound, but we wanted to do flags and we couldn't find a better solution", but this "it's deliberate and it's good" I just find difficult to get behind.


Andrei

April 07, 2014
On Monday, 7 April 2014 at 21:02:04 UTC, Andrei Alexandrescu wrote:
> This program compiles and flag free and no cast in sight but fails at runtime. Textbook example of unsound type design.
>
> import std.stdio;
>
> enum A { x = 2, y = 4 }
>
> void main()
> {
>     A a = A.x | A.y;
>     final switch (a)
>     {
>         case A.x: break;
>         case A.y: break;
>     }
> }
>
> The "|" operator converts back to an A. It shouldn't. In this case it provides a value not only outside the enum range, but even greater than A.max (when converted to integer).
>
> I'm fine with "yes, it's unsound, but we wanted to do flags and we couldn't find a better solution", but this "it's deliberate and it's good" I just find difficult to get behind.
>
>
> Andrei

Yeah, I've seen this happen before. I think we could actually introduce a little more type safety on enums without a great deal of breakage. It would be nice to have a final switch give you as much of a guarantee about what it's doing as it can.
April 08, 2014
Dicebot, el  7 de April a las 12:04 me escribiste:
> On Monday, 7 April 2014 at 10:07:03 UTC, Regan Heath wrote:
> >Got a DIP/spec/design to share?
> >
> >R
> 
> I think biggest mistake of D enums is merging constants and actual enumerations into single entity which has resulted in weak typing of enumerations.

Yeah,

enum E { A = 1, B = 2 }

is the same as:

struct E { immutable A = 1, B = 2; }

(leaving storage aside)

Which for me it doesn't make any sense. Even thinking about the argument (you should have a big gain to introduce syntax sugar).

enums should be enums, flags should be flags, and manifest constants should be... well, you got the idea.

enum is to D what const is to C++ :P

-- 
Leandro Lucarella (AKA luca)                     http://llucax.com.ar/
----------------------------------------------------------------------
The average person laughs 13 times a day
April 08, 2014
Using Ada code examples below:

On Monday, 7 April 2014 at 16:25:45 UTC, Regan Heath wrote:
> On Mon, 07 Apr 2014 16:15:41 +0100, Paulo Pinto <pjmlp@progtools.org> wrote:
>
>> Am 07.04.2014 12:07, schrieb Regan Heath:
>>> On Mon, 07 Apr 2014 00:17:45 +0100, Andrei Alexandrescu
>>> <SeeWebsiteForEmail@erdani.org> wrote:
>>>
>>>> On 4/6/14, 10:52 AM, Walter Bright wrote:
>>>>> On 4/6/2014 3:31 AM, Leandro Lucarella wrote:
>>>>>> What I mean is the current semantics of enum are as they are for
>>>>>> historical reasons, not because they make (more) sense (than other
>>>>>> possibilities). You showed a lot of examples that makes sense only
>>>>>> because you are used to the current semantics, not because they are the
>>>>>> only option or the option that makes the most sense.
>>>>>
>>>>> I use enums a lot in D. I find they work very satisfactorily. The way
>>>>> they work was deliberately designed, not a historical accident.
>>>>
>>>> Sorry, I think they ought to have been better. -- Andrei
>>>
>>> Got a DIP/spec/design to share?
>>>
>>> R
>>>
>>
>> How they work in languages like Ada.
>
> Ok, brief look at those shows me enums can be converted to a "Pos" index but otherwise you cannot associate a numberic value with them, right?
>
> So if we had that in D, Walters examples would look like..
>
> 1)
>
>   enum Index { A, B, C }
>   T[Index.C.pos + 1] array; // perhaps?


type Index is (A, B, C);

d_array: array Index'Length of T;


>   ...
>   array[Index.B.pos] = t;   // yes?

d_array(Index'Pos(B)) := t;

>
> 2)
>
>   array[Index.A.pos + 1] = t; // yes?

d_array(Index'Succ(A)) := t;

>
> 3)
>
>   enum Mask { A=1,B=4 } // not possible?
>
>   Mask m = A | B;   // Error: incompatible operator | for enum


type Mask is (A, B);
for Mask use (A => 1, B => 4);

m : Mask := Mask'Pos(A) or Mask'Pos(B);

>
>
> Have I got that right?
>
> For a proposal like this to even be considered I would imagine it would have to be backward compatible with existing uses, so you would have to be proposing a new keyword or syntax on "enum" to trigger typesafe enums, perhaps "typesafe" is a good keyword, e.g.
>
> typesafe enum Index { A, B, C } // requires use of .pos to convert to int 0, 1, or 2.
> enum Index { A, B, C }          // existing pragmatic behaviour
>
> R


This is the C++ approach with enum class for strong typed enums.
April 08, 2014
(moving http://goo.gl/ZISWwN to this group)

On 4/7/14, 3:41 PM, w0rp wrote:
> Yeah, I've seen this happen before. I think we could actually introduce
> a little more type safety on enums without a great deal of breakage. It
> would be nice to have a final switch give you as much of a guarantee
> about what it's doing as it can.

People use enums for things such as:

1. A discrete set of categorical values:

enum State { initial, waiting, running, done }

That's probably the most common use, and the one that prompted the moniker "enum(eration)".

On these, virtually no arithmetic makes any sense. At most, some notion of State successor(State) and State predecessor(State) may be sensible. It would return the next/previously naturally occurring discrete value, and would either throw, assert, or saturate at limits.

2. A collection of power-of-two flags that can be combined with "or" and picked apart with "and".

enum AccountFlags { active = 1, overdrawn, hasDiscount = 4, primary = 8 }

Often, people who define these enums offer names for frequently-encountered combinations of these flags:

enum AccountFlags { active = 1, overdrawn, hasDiscount = 4, primary = 8, regular = active | primary }

For these kinds of flags, arithmetic doesn't make sense, only bitwise operations: "|", "&", "^", and "~". A small algebra would be defined for these operations.

3. A discrete set of categorical values, not all of which are named:

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

There would be no arithmetic/logic for such enums - they are only to express categories. Comparisons for equality are needed; comparisons for inequality may or may not be needed.

4. A "clone" of a type (usually numeric) that's generally used as a helper for better typing.

enum Kilogram : double {}
enum Percent {}

All usual arithmetic is supposed to work. The enum acts as a subtype of its base type.

5. Various combinations of the above, for example flags combined with masks:

enum AccountFlags { codeMask = 7, active = 8, overdrawn = 16, hasDiscount = 32, primary = 64 }

(The account would start with a 3-bit code.) For such complex/irregular uses it may make sense to require casting to the base type of the enum before carrying general operations.

==========================

The current design is loose enough to accommodate all of the above uses, probably too loose because it allows a bunch of nonsensical code to compile. There are several questions to ask ourselves:

1. Is the current design damaging enough (= allows enough wrong/buggy code to pass through) to warrant a breaking tightening?

2. To what extent can library-based approaches help?

3. What is the priority of improving enums in the larger picture of other things we must do?


Andreiu
April 08, 2014
(moving http://goo.gl/ZISWwN to this group)

On 4/7/14, 3:41 PM, w0rp wrote:
> Yeah, I've seen this happen before. I think we could actually introduce
> a little more type safety on enums without a great deal of breakage. It
> would be nice to have a final switch give you as much of a guarantee
> about what it's doing as it can.

People use enums for things such as:

1. A discrete set of categorical values:

enum State { initial, waiting, running, done }

That's probably the most common use, and the one that prompted the moniker "enum(eration)".

On these, virtually no arithmetic makes any sense. At most, some notion of State successor(State) and State predecessor(State) may be sensible. It would return the next/previously naturally occurring discrete value, and would either throw, assert, or saturate at limits.

2. A collection of power-of-two flags that can be combined with "or" and picked apart with "and".

enum AccountFlags { active = 1, overdrawn, hasDiscount = 4, primary = 8 }

Often, people who define these enums offer names for frequently-encountered combinations of these flags:

enum AccountFlags { active = 1, overdrawn, hasDiscount = 4, primary = 8, regular = active | primary }

For these kinds of flags, arithmetic doesn't make sense, only bitwise operations: "|", "&", "^", and "~". A small algebra would be defined for these operations.

3. A discrete set of categorical values, not all of which are named:

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

There would be no arithmetic/logic for such enums - they are only to express categories. Comparisons for equality are needed; comparisons for inequality may or may not be needed.

4. A "clone" of a type (usually numeric) that's generally used as a helper for better typing.

enum Kilogram : double {}
enum Percent {}

All usual arithmetic is supposed to work. The enum acts as a subtype of its base type.

5. Various combinations of the above, for example flags combined with masks:

enum AccountFlags { codeMask = 7, active = 8, overdrawn = 16, hasDiscount = 32, primary = 64 }

(The account would start with a 3-bit code.) For such complex/irregular uses it may make sense to require casting to the base type of the enum before carrying general operations.

==========================

The current design is loose enough to accommodate all of the above uses, probably too loose because it allows a bunch of nonsensical code to compile. There are several questions to ask ourselves:

1. Is the current design damaging enough (= allows enough wrong/buggy code to pass through) to warrant a breaking tightening?

2. To what extent can library-based approaches help?

3. What is the priority of improving enums in the larger picture of other things we must do?


Andrei
April 08, 2014
On Tue, 08 Apr 2014 15:08:46 -0400, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> wrote:

> (moving http://goo.gl/ZISWwN to this group)
>
> On 4/7/14, 3:41 PM, w0rp wrote:
>> Yeah, I've seen this happen before. I think we could actually introduce
>> a little more type safety on enums without a great deal of breakage. It
>> would be nice to have a final switch give you as much of a guarantee
>> about what it's doing as it can.
>
> People use enums for things such as:
>
> 1. A discrete set of categorical values:
>
> enum State { initial, waiting, running, done }
>
> That's probably the most common use, and the one that prompted the moniker "enum(eration)".
>
> On these, virtually no arithmetic makes any sense. At most, some notion of State successor(State) and State predecessor(State) may be sensible. It would return the next/previously naturally occurring discrete value, and would either throw, assert, or saturate at limits.

I don't think this is necessarily true. Any kind of assumed order may be incorrect.

> 2. A collection of power-of-two flags that can be combined with "or" and picked apart with "and".
>
> enum AccountFlags { active = 1, overdrawn, hasDiscount = 4, primary = 8 }

That overdrawn without a specific value!!! Please, nobody do that :)

> 3. A discrete set of categorical values, not all of which are named:
>
> enum UserID : ulong { nobody, expired = ulong.max }
>
> There would be no arithmetic/logic for such enums - they are only to express categories. Comparisons for equality are needed; comparisons for inequality may or may not be needed.
>
> 4. A "clone" of a type (usually numeric) that's generally used as a helper for better typing.
>
> enum Kilogram : double {}
> enum Percent {}
>
> All usual arithmetic is supposed to work. The enum acts as a subtype of its base type.

I haven't seen that use in a while. Is that something we want to promote?

> The current design is loose enough to accommodate all of the above uses, probably too loose because it allows a bunch of nonsensical code to compile. There are several questions to ask ourselves:
>
> 1. Is the current design damaging enough (= allows enough wrong/buggy code to pass through) to warrant a breaking tightening?
>
> 2. To what extent can library-based approaches help?
>
> 3. What is the priority of improving enums in the larger picture of other things we must do?

What about allowing enum methods? e.g.:

enum State
{
   initial, waiting, running, done;
   State next() { if(this == done) return done; return State(cast(int)this + 1);}
}

I'm not sure what benefits this has over just defining a struct instead. In fact, an enum-as-a-type is essentially an easy-to-define wrapper struct.

Not sure if it's relevant to this discussion, but there are also anonymous enums.

-Steve
April 08, 2014
On 4/8/14, 12:23 PM, Steven Schveighoffer wrote:
> What about allowing enum methods?

No because CTFE. -- Andrei

April 08, 2014
On Tue, 08 Apr 2014 15:51:31 -0400, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> wrote:

> On 4/8/14, 12:23 PM, Steven Schveighoffer wrote:
>> What about allowing enum methods?
>
> No because CTFE. -- Andrei

What about what I said is prevented by or prevents CTFE (not sure what your objection is)?

-Steve
April 08, 2014
On 4/8/14, 12:54 PM, Steven Schveighoffer wrote:
> On Tue, 08 Apr 2014 15:51:31 -0400, Andrei Alexandrescu
> <SeeWebsiteForEmail@erdani.org> wrote:
>
>> On 4/8/14, 12:23 PM, Steven Schveighoffer wrote:
>>> What about allowing enum methods?
>>
>> No because CTFE. -- Andrei
>
> What about what I said is prevented by or prevents CTFE (not sure what
> your objection is)?

CTFE renders enum methods unnecessary. -- Andrei

2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18