www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Wrong enum comparisons

reply "bearophile" <bearophileHUGS lycos.com> writes:
In some code I have created a small bug that can be reduced to 
something like this, that the D compiler has not caught at 
compile-time:


enum E1 { A, B }
enum E2 { C, D }
void main() {
     E1[2] a;
     with (E2)
         assert(a[0] == D);
}


Why isn't D able to statically tell when you compare values of 
different enums?
How much work is implementing this compile-time test inside the D 
front-end?

Thank you,
bye,
bearophile
May 27 2012
next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
The *real* question is, why don't you need the "E2" qualifier 
when you say "D"?
May 27 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 28 May 2012 at 05:33:28 UTC, Mehrdad wrote:
 The *real* question is, why don't you need the "E2" qualifier 
 when you say "D"?

well never mind, I need sleep... I didn't see you were using 'with'.
May 27 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, May 27, 2012 21:45:22 bearophile wrote:
 In some code I have created a small bug that can be reduced to
 something like this, that the D compiler has not caught at
 compile-time:
 
 
 enum E1 { A, B }
 enum E2 { C, D }
 void main() {
      E1[2] a;
      with (E2)
          assert(a[0] == D);
 }
 
 
 Why isn't D able to statically tell when you compare values of
 different enums?
 How much work is implementing this compile-time test inside the D
 front-end?

If assert(E1.A == E2.D) wouldn't compile, then this is clearly a bug with with. If assert(E1.A == E2.d) compiles, then this behavior is clearly intended. My guess would be that this is a bug with with, but I don't think that I've ever tried to compare two different types of enums like that before, so I'd have to check. - Jonathan M Davis
May 27 2012
prev sibling next sibling parent reply Denis Shelomovskij <verylonglogin.reg gmail.com> writes:
27.05.2012 23:45, bearophile написал:
 In some code I have created a small bug that can be reduced to something
 like this, that the D compiler has not caught at compile-time:


 enum E1 { A, B }
 enum E2 { C, D }
 void main() {
 E1[2] a;
 with (E2)
 assert(a[0] == D);
 }


 Why isn't D able to statically tell when you compare values of different
 enums?
 How much work is implementing this compile-time test inside the D
 front-end?

 Thank you,
 bye,
 bearophile

Enumerations are in very poor state in D now. May be, it should be deprecated just like typedef and be library-implemented. Why? Because we do need really strict type/operation checking with enum so one have to write explicitly casts to do non-standard things. The two main enumeration types are: * list - must hold only one value, only opAssign and opEqual are allowed, by default nextValue = prevValue + 1 starting with 0 * flags - must hold superposition of values, like list, but binary operations are also allowed, by default nextValue = prevValue << 1 starting with 1 These also allow finally implement right to!string for flags. By the way, current enums can be modified to correspond "list" enumeration and flags can be added as library component. -- Денис В. Шеломовский Denis V. Shelomovskij
May 28 2012
next sibling parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
On 28.05.2012 12:58, Denis Shelomovskij wrote:
 27.05.2012 23:45, bearophile написал:
 In some code I have created a small bug that can be reduced to something
 like this, that the D compiler has not caught at compile-time:


 enum E1 { A, B }
 enum E2 { C, D }
 void main() {
 E1[2] a;
 with (E2)
 assert(a[0] == D);
 }


 Why isn't D able to statically tell when you compare values of different
 enums?
 How much work is implementing this compile-time test inside the D
 front-end?

 Thank you,
 bye,
 bearophile

Enumerations are in very poor state in D now. May be, it should be deprecated just like typedef and be library-implemented. Why? Because we do need really strict type/operation checking with enum so one have to write explicitly casts to do non-standard things. The two main enumeration types are: * list - must hold only one value, only opAssign and opEqual are allowed, by default nextValue = prevValue + 1 starting with 0 * flags - must hold superposition of values, like list, but binary operations are also allowed, by default nextValue = prevValue << 1 starting with 1

+1. Well said, hard to add anything ;)
 These also allow finally implement right to!string for flags.

 By the way, current enums can be modified to correspond "list"
 enumeration and flags can be added as library component.

-- Dmitry Olshansky
May 28 2012
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 5/28/12 8:19 AM, foobar wrote:
 I have to loudly object to this definition. Given a typical enumeration
 such as:
 enum color {Blue, Green, Red};
 Who's to say that Blue must equal 0? This is conceptually plain *wrong*.

 A conceptually correct enumeration must NOT expose such implementation
 details as the very poor C/D style enums do.

Depends on what the concept is. The archetype for "enumerating" is natural numbers, and in that view there's nothing wrong to attach a natural ordinal to each element of an enumeration. I do agree that it's wrong to _conflate_ the enumerated value with it ordinal, so in this program neither comparison should compile without an explicit cast: enum E1 { A, B } enum E2 { C, D } void main() { E1 a; assert(a == 0); assert(a == E2.C); } The first one is probably difficult to disallow at this time, but the second one almost always indicates a bug, confusion, or abuse on the user side. We should disallow it.
 See functional languages
 such as ML for a correct implementation and also Java 5 Enums (similar
 but with an OO flavor).

I've always found it amusing to watch what a kitchen sink Java enumerations have become. It's as if someone said, "so you complain Java doesn't have enum? I'll give you enum!" I think we shouldn't copy that design. Andrei
May 28 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 5/28/12 1:07 PM, foobar wrote:
 It depends on what exactly the general concept is, is this a predefined
 set of values or is it an ordered list. I'd argue that the set is more
 general and we shouldn't force an ordering when one isn't strictly
 required. Of course, the programmer should be able to add an ordering
 when it's required.

Ascribing ordinal values is not ordering.
 How to implement an order:
 IMO casts usually indicate code smell.
 I think something like Color.Red.ordinal states the intention much
 better than a cast.

Well ordinal could always be a function that does the cast...
 Could you please explain the issues you see in Java's Enum that cause
 you to reject such a design?

Too big for what it does. Not worth getting into details as I don't have a strong opinion or one of much consequence. At the end of the day, it is fairly clear to me that D's enum is in no interesting point on the convenience/safety tradeoff scale, and doesn't offer anything worth its presence in the language. It's just a bit less broken than typedef, which was just egregious. No surprise there - aspects of the language that received attention (e.g. floating point) turned out well whereas those that didn't (either through incomplete analysis or inheriting a bad design) turned out rather badly. I'd put typedef (good thing it's gone), lazy, and enum in the latter category. Andrei
May 28 2012
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 5/28/12 2:37 PM, Jonathan M Davis wrote:
 enum does need some work, but I think that the fact that it can be both built-
 in types such as int as well as user-defined structs is great. It essentially
 gives us the best of both worlds (basic enums such as in C/C++ and more
 complex types as in Java). It's some of the minor details where things tend to
 not work as well as would be nice (e.g. some of the arithmetic that's
 considered legal on enums which are integral values).

I agree it is possible to fix enum. Andrei
May 28 2012
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Denis Shelomovskij:

 Enumerations are in very poor state in D now.
 ...
 By the way, current enums can be modified to correspond "list" 
 enumeration and flags can be added as library component.

I think D enums need to become a bit more strict (so you can't equal elements of different enums). Later I think the messy situation with enum attributes needs to be cleared up and improved, removing most of the name clases between the current attributes and enum item names (http://d.puremagic.com/issues/show_bug.cgi?id=4997 ). And later *simple* flags are implementable in Phobos. First step is to make them less sloppy and more strict, otherwise successive work will be founded on quicksand (but as long as such threads generate no concrete answers from D designers, we are just wasting time here). In GitHub there are many patches open, applying several of them will reduce many current pain points of using D. Bye, bearophile
May 28 2012
prev sibling next sibling parent "Araq" <rumpf_a web.de> writes:
Pascal got type safe enums and sets of enums (aka "flags") right 
in the 60ies. Too bad not even Ada copied this nice feature. 
Fortunately, Nimrod does.
May 28 2012
prev sibling next sibling parent "foobar" <foo bar.com> writes:
On Monday, 28 May 2012 at 08:58:29 UTC, Denis Shelomovskij wrote:
 27.05.2012 23:45, bearophile написал:
 In some code I have created a small bug that can be reduced to 
 something
 like this, that the D compiler has not caught at compile-time:


 enum E1 { A, B }
 enum E2 { C, D }
 void main() {
 E1[2] a;
 with (E2)
 assert(a[0] == D);
 }


 Why isn't D able to statically tell when you compare values of 
 different
 enums?
 How much work is implementing this compile-time test inside 
 the D
 front-end?

 Thank you,
 bye,
 bearophile

Enumerations are in very poor state in D now. May be, it should be deprecated just like typedef and be library-implemented. Why? Because we do need really strict type/operation checking with enum so one have to write explicitly casts to do non-standard things. The two main enumeration types are: * list - must hold only one value, only opAssign and opEqual are allowed, by default nextValue = prevValue + 1 starting with 0 * flags - must hold superposition of values, like list, but binary operations are also allowed, by default nextValue = prevValue << 1 starting with 1 These also allow finally implement right to!string for flags. By the way, current enums can be modified to correspond "list" enumeration and flags can be added as library component.

I have to loudly object to this definition. Given a typical enumeration such as: enum color {Blue, Green, Red}; Who's to say that Blue must equal 0? This is conceptually plain *wrong*. A conceptually correct enumeration must NOT expose such implementation details as the very poor C/D style enums do. See functional languages such as ML for a correct implementation and also Java 5 Enums (similar but with an OO flavor). The second point is conceptually *wrong* as well - a set of flag values is exactly that - _a set_. The type-safe correct way is to use a set!MyEnum to properly represent this (of course with a specialized implementation for this using bits for performance reasons).
May 28 2012
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Andrei Alexandrescu:

 I do agree that it's wrong to _conflate_ the enumerated value 
 with it ordinal, so in this program neither comparison should 
 compile without an explicit cast:

 enum E1 { A, B }
 enum E2 { C, D }

 void main() {
     E1 a;
     assert(a == 0);
     assert(a == E2.C);
 }

 The first one is probably difficult to disallow at this time,

I filed this first problem in 2010-03 (I have not filed the Enum1==Enum2 problem yet): http://d.puremagic.com/issues/show_bug.cgi?id=3999 This is a small breaking change, but it doesn't introduce bugs in already written code, it turns into compile-time errors code that currently compiles. I think the usual way to solve this compile-time error is to use a cast (or sometimes use this safe to!() enhancement: http://d.puremagic.com/issues/show_bug.cgi?id=8143 ). C++11 has "solved" this compatibility problem creating another kind of enum, referred as "enum class": enum class Foo { V1 = 10 }; int main() { int b = Foo::V1 == 10; } test.cpp: In function 'int main()': test.cpp:3: error: no match for 'operator==' in '(Foo)10 == 10' test.cpp:3: note: candidates are: operator==(int, int) <built-in> The disadvantage of doing this is the complexity increase of the language. I don't know how do you estimate how much difficult is to perform small breaking changes in the language. Bye, bearophile
May 28 2012
prev sibling next sibling parent "foobar" <foo bar.com> writes:
On Monday, 28 May 2012 at 13:47:47 UTC, Andrei Alexandrescu wrote:
 On 5/28/12 8:19 AM, foobar wrote:
 I have to loudly object to this definition. Given a typical 
 enumeration
 such as:
 enum color {Blue, Green, Red};
 Who's to say that Blue must equal 0? This is conceptually 
 plain *wrong*.

 A conceptually correct enumeration must NOT expose such 
 implementation
 details as the very poor C/D style enums do.

Depends on what the concept is. The archetype for "enumerating" is natural numbers, and in that view there's nothing wrong to attach a natural ordinal to each element of an enumeration. I do agree that it's wrong to _conflate_ the enumerated value with it ordinal, so in this program neither comparison should compile without an explicit cast: enum E1 { A, B } enum E2 { C, D } void main() { E1 a; assert(a == 0); assert(a == E2.C); } The first one is probably difficult to disallow at this time, but the second one almost always indicates a bug, confusion, or abuse on the user side. We should disallow it.
 See functional languages
 such as ML for a correct implementation and also Java 5 Enums 
 (similar
 but with an OO flavor).

I've always found it amusing to watch what a kitchen sink Java enumerations have become. It's as if someone said, "so you complain Java doesn't have enum? I'll give you enum!" I think we shouldn't copy that design. Andrei

It depends on what exactly the general concept is, is this a predefined set of values or is it an ordered list. I'd argue that the set is more general and we shouldn't force an ordering when one isn't strictly required. Of course, the programmer should be able to add an ordering when it's required. How to implement an order: IMO casts usually indicate code smell. I think something like Color.Red.ordinal states the intention much better than a cast. Could you please explain the issues you see in Java's Enum that cause you to reject such a design?
May 28 2012
prev sibling next sibling parent Marco Leise <Marco.Leise gmx.de> writes:
Am Mon, 28 May 2012 13:48:23 +0200
schrieb "Araq" <rumpf_a web.de>:

 Pascal got type safe enums and sets of enums (aka "flags") right 
 in the 60ies. Too bad not even Ada copied this nice feature. 
 Fortunately, Nimrod does.

Yes, I really like Pascal for that feature. Actually it is a mix of features I miss from Pascal in D: 1. Ranged ordinal types type Month = 1 .. 12; type AsciiLower = 'a' .. 'z'; type Hours = 0 .. 24; As far as I remember it, the compiler chooses the optimal ordinal type derived from the start and end constant. It is a form of self-documenting code and it could work nice with "checked integers", if they are ever implemented in D. 2. Enumerations type Day = ( Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday ); Ord(x) gives the ordinal value of an enumeration variable, e.g. neither implicit conversion to integer types nor casts, but a language defined, clean way to convert from enums to their inherent integer representation. 3. Sets I'd like to start with the enumeration for the days of a week, because I think both Pascal and D look intuitive here: Pascal: type Day = ( Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday ); var weekday : Day; weekday = Monday; weekday = Succ(weekday); // Tuesday D: enum Day { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday } Day weekday = Monday; weekday++; // Tuesday But D lacks proper sets, which turn any enumeration into a set: type DaySet = set of Day; Note how Pascal naturally maps the enum values (0 through 6) to 1, 2, 4, 8, 16, 32 and 64 internally, while in C or D you need to do this manually or by importing a module that offers a template solution. Further you can apply the typical operators to sets: var workdays : DaySet; workdays = [ Monday .. Friday ]; // pretty nice shortcut to set a range of flags if Monday in workdays then ... // check if set contains a value weekdays = weekdays + [ Monday, Sunday ] // union (adds Sunday) weekdays = weekdays - [ Monday, Sunday ] // complement (removes Monday) weekdays = weekdays * [ Monday, Sunday ] // intersection (leaves Monday) // (in)euqality, subset and superset are also implemented I think flag sets are needed often enough (file open modes, drawing hints, enabled features) to warrant that feature and they enable some programming styles that are awkward with what C gives you at hand. 123. All these integrate nicely with each other Create an array that holds a value of 0 to 24 for each week day: var hoursPerDay : array[Day] of Hours; hoursPerDay[Monday] = 8; In general I had a good experience with Pascal when it comes to defining and checking flags. -- Marco
May 28 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, May 28, 2012 14:21:14 Andrei Alexandrescu wrote:
 On 5/28/12 1:07 PM, foobar wrote:
 It depends on what exactly the general concept is, is this a predefined
 set of values or is it an ordered list. I'd argue that the set is more
 general and we shouldn't force an ordering when one isn't strictly
 required. Of course, the programmer should be able to add an ordering
 when it's required.

Ascribing ordinal values is not ordering.
 How to implement an order:
 IMO casts usually indicate code smell.
 I think something like Color.Red.ordinal states the intention much
 better than a cast.

Well ordinal could always be a function that does the cast...
 Could you please explain the issues you see in Java's Enum that cause
 you to reject such a design?

Too big for what it does. Not worth getting into details as I don't have a strong opinion or one of much consequence. At the end of the day, it is fairly clear to me that D's enum is in no interesting point on the convenience/safety tradeoff scale, and doesn't offer anything worth its presence in the language. It's just a bit less broken than typedef, which was just egregious. No surprise there - aspects of the language that received attention (e.g. floating point) turned out well whereas those that didn't (either through incomplete analysis or inheriting a bad design) turned out rather badly. I'd put typedef (good thing it's gone), lazy, and enum in the latter category.

enum does need some work, but I think that the fact that it can be both built- in types such as int as well as user-defined structs is great. It essentially gives us the best of both worlds (basic enums such as in C/C++ and more complex types as in Java). It's some of the minor details where things tend to not work as well as would be nice (e.g. some of the arithmetic that's considered legal on enums which are integral values). - Jonathan M Davis
May 28 2012
prev sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Andrei Alexandrescu:

 I do agree that it's wrong to _conflate_ the enumerated value 
 with it ordinal,

 I agree it is possible to fix enum.

Thank you for your answers Andrei. I have filed an enhancement request: http://d.puremagic.com/issues/show_bug.cgi?id=8157 Issue 8157 is a subset of issue 3999, so they don't interfere with each other in Bugzilla. I'd like to receive a comment from Walter :-) Bye, bearophile
May 28 2012