www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - enum inheritance

reply "JS" <js.mdnq gmail.com> writes:
It would be nice to be able to use enums in a hierarchical way:

enum colors
{
      enum Red { RedOrange, ... }
      enum Green { GreenBlue, ...}
      enum Blue { BlueYellow, ... }
...
}

which would be the same as the flattened version,

enum colors
{
    Red, RedOrange, ..., Green, GreenBlue, ..., Blue, BlueYellow, 
..., ...
}

but we could dereference such as

colors.Red.RedOrange,
colors.Blue,
colors.Green.GreenBlue,

(This isn't a great example but demonstrates what I would like to 
be able to do)

Is anything like this possible?
Jul 14 2013
next sibling parent reply "JS" <js.mdnq gmail.com> writes:
BTW, the usefulness is to group sub-enums into the same range. 
This would make it easy/efficient to branch over a range in the 
enum:

if (v in colors.Red) { v is a color in red }

instead of

if (v is color.Red || v is color.RedOrange || ...)
Jul 14 2013
next sibling parent "JS" <js.mdnq gmail.com> writes:
On Monday, 15 July 2013 at 04:27:42 UTC, JS wrote:
 BTW, the usefulness is to group sub-enums into the same range. 
 This would make it easy/efficient to branch over a range in the 
 enum:

 if (v in colors.Red) { v is a color in red }

 instead of

 if (v is color.Red || v is color.RedOrange || ...)
I guess a more intelligent structure is needed so changes in the enum do not break binaries. (although this is already somewhat of an issue with enums as changes in order requires a re-compilation)
Jul 14 2013
prev sibling parent reply "Mike Parker" <aldacron gmail.com> writes:
On Monday, 15 July 2013 at 04:27:42 UTC, JS wrote:
 BTW, the usefulness is to group sub-enums into the same range. 
 This would make it easy/efficient to branch over a range in the 
 enum:

 if (v in colors.Red) { v is a color in red }

 instead of

 if (v is color.Red || v is color.RedOrange || ...)
if( v >= Red && v <= LastRed )
Jul 15 2013
parent reply "JS" <js.mdnq gmail.com> writes:
On Monday, 15 July 2013 at 07:37:36 UTC, Mike Parker wrote:
 On Monday, 15 July 2013 at 04:27:42 UTC, JS wrote:
 BTW, the usefulness is to group sub-enums into the same range. 
 This would make it easy/efficient to branch over a range in 
 the enum:

 if (v in colors.Red) { v is a color in red }

 instead of

 if (v is color.Red || v is color.RedOrange || ...)
if( v >= Red && v <= LastRed )
The problem is if a binary is already compiled and changes are made. If a red color is inserted your range has changed. Also, it requires you to keep your entries sorted properly. Also, a "LastRed" entry is needed and there is no other reason to have it. Since an int is 4B entries, and that's way more than most will use, It think it's better to be able to have the compiler partition of the range for you and save "space" for future entries. This prevents an addition entry from screwing up everything. e.g., enum colors { enum Red : 1M { RedOrange, ... } enum Green : 2M { ... } ... } In this case, one can have up to 1M unordered sub entries(should be plenty) and ~4.2k main entries. One could order further, enum colors { enum Red : 1M { enum RedOrange : 10k { }, ... } enum Green : 2M { ... } ... } Basically the idea is to distribute the elements of enum and sub enums in such a way as to maximize space between each entry. This allows any binaries using an old version not to crash and burn. This does require an estimation of the maximum entries that will be used. I'm particularly thinking of a messaging system where ints can be passed around with 10's of thousands of defined messages with messages grouped into common functionality(hence the inheritance aspect) and adding new messages won't ruin the communications between new and old systems. Nested switch statements can easily and quickly pare down determination of a message(not as fast as a flattened hierarchy though).
Jul 15 2013
parent reply "Namespace" <rswhite4 googlemail.com> writes:
Maybe this way?

----
final abstract class Colors
{
	enum Red { RedOrange }
	enum Green { GreenBlue}
	enum Blue { BlueYellow }
}

void main() {
	Colors.Red foo = Colors.Red.RedOrange;
	assert(foo >= Colors.Red.min && foo <= Colors.Red.max);
}
----
Jul 15 2013
parent reply "JS" <js.mdnq gmail.com> writes:
On Monday, 15 July 2013 at 08:20:20 UTC, Namespace wrote:
 Maybe this way?

 ----
 final abstract class Colors
 {
 	enum Red { RedOrange }
 	enum Green { GreenBlue}
 	enum Blue { BlueYellow }
 }

 void main() {
 	Colors.Red foo = Colors.Red.RedOrange;
 	assert(foo >= Colors.Red.min && foo <= Colors.Red.max);
 }
 ----
but RedOrange and GreenBlue have the same value!
Jul 15 2013
parent reply "JS" <js.mdnq gmail.com> writes:
On Monday, 15 July 2013 at 10:17:08 UTC, JS wrote:
 On Monday, 15 July 2013 at 08:20:20 UTC, Namespace wrote:
 Maybe this way?

 ----
 final abstract class Colors
 {
 	enum Red { RedOrange }
 	enum Green { GreenBlue}
 	enum Blue { BlueYellow }
 }

 void main() {
 	Colors.Red foo = Colors.Red.RedOrange;
 	assert(foo >= Colors.Red.min && foo <= Colors.Red.max);
 }
 ----
but RedOrange and GreenBlue have the same value!
Also, Colors.Red is not a value!
Jul 15 2013
parent "Namespace" <rswhite4 googlemail.com> writes:
On Monday, 15 July 2013 at 10:23:22 UTC, JS wrote:
 On Monday, 15 July 2013 at 10:17:08 UTC, JS wrote:
 On Monday, 15 July 2013 at 08:20:20 UTC, Namespace wrote:
 Maybe this way?

 ----
 final abstract class Colors
 {
 	enum Red { RedOrange }
 	enum Green { GreenBlue}
 	enum Blue { BlueYellow }
 }

 void main() {
 	Colors.Red foo = Colors.Red.RedOrange;
 	assert(foo >= Colors.Red.min && foo <= Colors.Red.max);
 }
 ----
but RedOrange and GreenBlue have the same value!
Also, Colors.Red is not a value!
Then: good luck.
Jul 15 2013
prev sibling next sibling parent reply "Dicebot" <public dicebot.lv> writes:
On Monday, 15 July 2013 at 04:24:59 UTC, JS wrote:
 ...
I think closest solution you can get is having bunch of private enum definitions and combining them into single public one via compile-time reflection. Something like "mixin(generateEnum!(Red, Green, blue))".
Jul 15 2013
parent reply "JS" <js.mdnq gmail.com> writes:
On Monday, 15 July 2013 at 11:00:59 UTC, Dicebot wrote:
 On Monday, 15 July 2013 at 04:24:59 UTC, JS wrote:
 ...
I think closest solution you can get is having bunch of private enum definitions and combining them into single public one via compile-time reflection. Something like "mixin(generateEnum!(Red, Green, blue))".
That might solve the partitioning problem and solve part of the hierarchical problem but won't allow submember access, e.g., colors.red.redorange. It seems like a good start though. I imagine one could potentially build up a set of nested struct with immutable members representing the enums: final immutable struct Colors { final immutable struct Red { private immutable int _Red = 10000; immutable int RedOrange = 10001; alias _Red this; // obviously doesn't work } final immutable struct Green { immutable int Green = 20000; } } but with the glitch on the alias(Colors.Red doesn't work)... which sort of throws a kink in the solution making more than a 2-deep nest useless.
Jul 15 2013
parent reply "Dicebot" <public dicebot.lv> writes:
On Monday, 15 July 2013 at 13:01:05 UTC, JS wrote:
 ...
I see. No, unfortunately, I am currently not aware of a way to make symbol act as type and and value at the same time. However it is worth noting that you use plenty of excessive attributes. Currently it is not a compiler error but makes code much harder to read and confusing. For example, "final" has no meaning for structs as they can't be inherited from anyway. Duplicating immutable is also excessive because it is transitive.
Jul 15 2013
parent reply "JS" <js.mdnq gmail.com> writes:
On Monday, 15 July 2013 at 13:47:10 UTC, Dicebot wrote:
 On Monday, 15 July 2013 at 13:01:05 UTC, JS wrote:
 ...
I see. No, unfortunately, I am currently not aware of a way to make symbol act as type and and value at the same time. However it is worth noting that you use plenty of excessive attributes. Currently it is not a compiler error but makes code much harder to read and confusing. For example, "final" has no meaning for structs as they can't be inherited from anyway. Duplicating immutable is also excessive because it is transitive.
Original I had it as a class. I'm not sure if it matters much between a class and a struct though? In any case, I solved this problem by using an attribute to test instead of using isMutable. Obviously this requires adding a symbol but not a huge deal. e.g., Enum immutable struct ... and hasAttribute(T, Enum) replaces isMutable(T).
Jul 15 2013
parent reply "Dicebot" <public dicebot.lv> writes:
On Monday, 15 July 2013 at 18:26:26 UTC, JS wrote:
 Original I had it as a class. I'm not sure if it matters much 
 between a class and a struct though?
It does matter a lot. structs are value types, classes are polymorphic reference types. There is a nice summary table in docs: http://dlang.org/struct.html , please notice that "inheritance" and this "final" applies only for classes.
 In any case, I solved this problem by using an attribute to 
 test instead of using isMutable. Obviously this requires adding 
 a symbol but not a huge deal.

 e.g.,

  Enum immutable struct ...

 and hasAttribute(T, Enum) replaces isMutable(T).
Yeah, that may work, despite being not that pretty. I personally think it is better for now chose some not-that-elegant approach in your code, at least until enums will become more robust. Currently it tries to workaround some very basic type system assumptions, which rarely ends good.
Jul 15 2013
parent "JS" <js.mdnq gmail.com> writes:
On Monday, 15 July 2013 at 19:24:27 UTC, Dicebot wrote:
 On Monday, 15 July 2013 at 18:26:26 UTC, JS wrote:
 Original I had it as a class. I'm not sure if it matters much 
 between a class and a struct though?
It does matter a lot. structs are value types, classes are polymorphic reference types. There is a nice summary table in docs: http://dlang.org/struct.html , please notice that "inheritance" and this "final" applies only for classes.
um, but I'm only using the type at compile time. the immutable fields in it all result in compile time literals... the struct itself never gets used at run time... so there is no runtime difference(or shouldn't be) except maybe a little more wasted space with the class unless it is optimized out.
 In any case, I solved this problem by using an attribute to 
 test instead of using isMutable. Obviously this requires 
 adding a symbol but not a huge deal.

 e.g.,

  Enum immutable struct ...

 and hasAttribute(T, Enum) replaces isMutable(T).
Yeah, that may work, despite being not that pretty. I personally think it is better for now chose some not-that-elegant approach in your code, at least until enums will become more robust. Currently it tries to workaround some very basic type system assumptions, which rarely ends good.
I really don't have 10 years to wait for enums to become more robust and hope in the way I am trying to use them.
Jul 15 2013
prev sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
JS:

 It would be nice to be able to use enums in a hierarchical way:

 enum colors
 {
      enum Red { RedOrange, ... }
      enum Green { GreenBlue, ...}
      enum Blue { BlueYellow, ... }
 ...
 }

 which would be the same as the flattened version,

 enum colors
 {
    Red, RedOrange, ..., Green, GreenBlue, ..., Blue, 
 BlueYellow, ..., ...
 }

 but we could dereference such as

 colors.Red.RedOrange,
 colors.Blue,
 colors.Green.GreenBlue,

 (This isn't a great example but demonstrates what I would like 
 to be able to do)

 Is anything like this possible?
In Ada you can define ranged types, and there is a keyword to define their subtypes: type Day is (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday); -- Derived types, they are new incompatible with the original: type Business_Day is new Day range Monday .. Friday; type Weekend_Day is new Day range Saturday .. Sunday; -- Subtypes that convert implicitly to their supertype: subtype Business_Day is Day range Monday .. Friday; subtype Weekend_Day is Day range Saturday .. Sunday; subtype Dice_Throw is Integer range 1 .. 6; The Ada compiler enforces static typing where possible, and uses dynamic tests where it can't (and the dynamic tests can be disabled with a compiler switch). So if assign a generic integer to a Dice_Throw, the Ada compiler performs a run-time test to see if it's in the correct range. Ada code is based on similar things that avoid/catch lot of bugs. Bye, bearophile
Aug 02 2013