digitalmars.D - reflective enums
- Kevin Bealer (162/162) Feb 15 2007 People have occasionally asked here about being able to do things like
-
Derek Parnell
(9/10)
Feb 15 2007
Now we're talking ... I wonder what 1.006 can do
- Andrei Alexandrescu (See Website For Email) (11/17) Feb 15 2007 Probably something along the lines:
- renoX (163/166) Feb 16 2007 Very nice, thanks!
- Kevin Bealer (25/183) Feb 16 2007 You're right -- this should match the default enum{} behavior. I actual...
- renoX (33/43) Feb 16 2007 From my testing, it doesn't trigger a conflict, but I've only tested it...
- janderson (6/26) Feb 16 2007 Advantage: you would be able to reuse the code as a sub-set of something...
- janderson (2/33) Feb 16 2007
- renoX (11/47) Feb 16 2007 Thanks for the correction, your initial remark was puzzling me.
- Kevin Bealer (6/59) Feb 16 2007 I don't like the extra syntax -- in your example, the word 'enum'
- janderson (3/18) Feb 16 2007 Nice one man!
- Kevin Bealer (3/21) Feb 16 2007 Thanks! I should say as a formality, I release all this into the public...
- renoX (6/67) Feb 19 2007 How about we discuss our usecase first, then we could work on the API fo...
- Kevin Bealer (9/78) Feb 19 2007 Okay. A lot of the awkwardness in the code for the second one is due to...
People have occasionally asked here about being able to do things like foreach() over the value of the enums. The following code is probably obsolete now that 1.006 is out, but I'm posting it anyway. I think once I update to the next version of DMD I should be able to cut all this down to about 20 lines, but here it is for 1.005. The Enum template allows you to declare a struct that contains a definition of constants (like an enumeration) but also allows you to get the strings for the constants and foreach() over the data. Note: the lines of code in main() represent the entirety of testing on this code, so it probably has bugs and issues. Also, checking for invalid input to the templates was not important to me in this case. (I'm writing a toy project where I need (as so often happens) both an enum and a corresponding "enumToString" function. Reading all those links from the other day about LISP and LISP macros seems to have given me a temporary allergy to 'repeating myself'. So I figured I would see if I can solve the enum + string problem -- this is the result.) On the subject of 'bloat', if I comment out the template definitinos and the contents of main() below, the 'strip'ed version of the binary trims by about 1.5 k, which doesn't seem too bad. 234268 // all code shown below 232796 // no code, but imports 174368 // no code or imports (I don't really care about the 232k -- the C++ binaries I build at work are something like 130 MB in debug mode; we have a lot of libraries.) Kevin // -*- c++ -*- import std.stdio; import std.metastrings; import std.string; import std.file; template Find(char[] A, char[] B) { static if (A.length < B.length) { const int Find = -1; } else static if (A[0..B.length] == B) { const int Find = 0; } else static if (-1 == Find!(A[1..$], B)) { const int Find = -1; } else { const int Find = 1 + Find!(A[1..$], B); } }; template SplitFirst(char[] A, char[] B) { const int Location = Find!(A, B); static if (Location == -1) { const char[] First = A; const char[] Rest = ""; } else { const char[] First = A[0..Location]; const char[] Rest = A[Location+B.length..$]; } }; template ChompSpaces(char[] A) { static if (A.length) { static if (A[0] == ' ') { alias ChompSpaces!(A[1..$]) ChompSpaces; } else static if (A[$-1] == ' ') { alias ChompSpaces!(A[0..$-1]) ChompSpaces; } else { alias A ChompSpaces; } } else { const char[] ChompSpaces = ""; } }; template SplitChomp(char[] A, char[] B) { alias ChompSpaces!(SplitFirst!(A, B).First) First; alias ChompSpaces!(SplitFirst!(A, B).Rest) Rest; } template EnumName(char[] A) { alias SplitChomp!(SplitChomp!(A, ",").First, "=").First EnumName; } template EnumAssign(char[] A) { alias SplitChomp!(SplitChomp!(A, ",").First, "=").Rest EnumAssign; } template EnumValue(char[] A, int next) { static if (EnumAssign!(A) == "") { const int EnumValue = next; } else { const int EnumValue = mixin(ParseInteger!(EnumAssign!(A)).value); } } template BuildOneEnum(char[] A, int i) { const char[] BuildOneEnum = "const int "~EnumName!(A)~" = "~ ToString!(EnumValue!(A, i))~";\n"; } template BuildOneCase(char[] A, int i) { const char[] BuildOneCase = "case "~ToString!(EnumValue!(A, i))~ ": return \""~EnumName!(A)~"\";\n"; } template BuildOneApply(char[] A, int i) { const char[] BuildOneApply = "i="~ToString!(EnumValue!(A, i))~";"~ " rv=dg(i); if (rv) return rv;"; } template BuildEnums(char[] A, int i) { static if (SplitChomp!(A, ",").Rest.length == 0) { const char[] BuildEnums = BuildOneEnum!(A, EnumValue!(A, i)); } else { const char[] BuildEnums = BuildOneEnum!(SplitChomp!(A, ",").First, EnumValue!(A, i)) ~ BuildEnums!(SplitChomp!(A, ",").Rest, EnumValue!(A, i)+1); } } template BuildEnumCases(char[] A, int i) { static if (SplitChomp!(A, ",").Rest.length == 0) { const char[] BuildEnumCases = BuildOneCase!(A, EnumValue!(A, i)); } else { const char[] BuildEnumCases = BuildOneCase!(SplitChomp!(A, ",").First, EnumValue!(A, i)) ~ BuildEnumCases!(SplitChomp!(A, ",").Rest, EnumValue!(A, i)+1); } } template BuildEnumSwitch(char[] var, char[] A, int i) { const char[] BuildEnumSwitch = "switch("~var~") {"~ BuildEnumCases!(A, i) ~ "default: "~ " throw new Exception(\"enumeration out of range\");" "}"; } template BuildEnumApply(char[] A, int i) { static if (SplitChomp!(A, ",").Rest.length == 0) { const char[] BuildEnumApply = BuildOneApply!(A, EnumValue!(A, i)); } else { const char[] BuildEnumApply = BuildOneApply!(SplitChomp!(A, ",").First, EnumValue!(A, i))~ BuildEnumApply!(SplitChomp!(A, ",").Rest, EnumValue!(A, i)+1); } } struct Enum(char[] A) { mixin(BuildEnums!(A, 1)); static char[] getString(int x) { mixin(BuildEnumSwitch!("x", A, 1)); } int opApply(int delegate(inout int) dg) { int i, rv; mixin(BuildEnumApply!(A, 1)); return 0; } } int main(char[][] args) { alias Enum!("start, middle, end=10, ps, pps") PState; int p = PState.middle; writefln("p is %s, with name %s\n", p, PState.getString(p)); PState P; foreach(v; P) { writefln("Enum %s has name=%s", v, PState.getString(v)); } return 0; }
Feb 15 2007
On Fri, 16 Feb 2007 00:28:42 -0500, Kevin Bealer wrote:The Enum template ...Now we're talking ... I wonder what 1.006 can do <G> -- Derek (skype: derek.j.parnell) Melbourne, Australia "Justice for David Hicks!" 16/02/2007 4:34:51 PM
Feb 15 2007
Derek Parnell wrote:On Fri, 16 Feb 2007 00:28:42 -0500, Kevin Bealer wrote:Probably something along the lines: mixin ReflectiveEnum!(int, "a", "b", "c"); That generates int-based enums (enumerated values of arbitrary type will be possible, but hey, one thing at a time), along with parsing and printing code. So it's not about what features are directly useful, but instead the power is in the proverbial level of indirection. I can imagine lots of people could care less about esoteric compile-time stuff, but they'd be glad to use a better enum. AndreiThe Enum template ...Now we're talking ... I wonder what 1.006 can do <G>
Feb 15 2007
Kevin Bealer Wrote:People have occasionally asked here about being able to do things like foreach() over the value of the enums. The following code is probably obsolete now that 1.006 is out, but I'm posting it anyway.Very nice, thanks! I was trying to do the same thing (without much success, I'm not used to functional template programming) , so you saved me quite some time.. I have a few remarks: - why do Enum start the values at 1 instead of 0? IMHO it should follow the way 'enum' works otherwise developers may be confused. - why the name getString instead of the usual toString name? - I'm not sure if it's useful to 'optimise' compilation time, but there were a few call to SplitChomp that can be replaced by SplitFirst. - toFullString which returns "<enum name>(<enum value>)" can be useful too maybe, I wanted to do a concatenation reusing toString to avoid duplicating the code, but I didn't manage to, so I added a parameter instead. The code below has the modifications. I wonder if the reflective enums could be integrated into a library, I think that it would be useful. The only downside of this version is that the developer must use int instead of a separate type.. This is probably fixable. renoX import std.stdio; import std.metastrings; template Find(char[] A, char[] B) { static if (A.length < B.length) { const int Find = -1; } else static if (A[0..B.length] == B) { const int Find = 0; } else static if (-1 == Find!(A[1..$], B)) { const int Find = -1; } else { const int Find = 1 + Find!(A[1..$], B); } } template SplitFirst(char[] A, char[] B) { const int Location = Find!(A, B); static if (Location == -1) { const char[] First = A; const char[] Rest = ""; } else { const char[] First = A[0..Location]; const char[] Rest = A[Location+B.length..$]; } } template ChompSpaces(char[] A) { static if (A.length) { static if (A[0] == ' ') { alias ChompSpaces!(A[1..$]) ChompSpaces; } else static if (A[$-1] == ' ') { alias ChompSpaces!(A[0..$-1]) ChompSpaces; } else { alias A ChompSpaces; } } else { const char[] ChompSpaces = ""; } } template SplitChomp(char[] A, char[] B) { alias ChompSpaces!(SplitFirst!(A, B).First) First; alias ChompSpaces!(SplitFirst!(A, B).Rest) Rest; } template EnumName(char[] A) { alias SplitChomp!(SplitFirst!(A, ",").First, "=").First EnumName; } template EnumAssign(char[] A) { alias SplitChomp!(SplitFirst!(A, ",").First, "=").Rest EnumAssign; } template EnumValue(char[] A, int i) { static if (EnumAssign!(A) == "") { const int EnumValue = i; } else { const int EnumValue = mixin(ParseInteger!(EnumAssign!(A)).value); } } template BuildOneEnum(char[] A, int i) { const char[] BuildOneEnum = "const int "~EnumName!(A)~" = "~ ToString!(EnumValue!(A, i))~";\n"; } template BuildEnums(char[] A, int i) { static if (SplitChomp!(A, ",").Rest.length == 0) { const char[] BuildEnums = BuildOneEnum!(A, EnumValue!(A, i)); } else { const char[] BuildEnums = BuildOneEnum!(SplitChomp!(A, ",").First, EnumValue!(A, i)) ~ BuildEnums!(SplitChomp!(A, ",").Rest, EnumValue!(A, i)+1); } } template BuildOneCase(char[] A, int i, bool full) { static if (!full) { const char[] BuildOneCase = "case "~ToString!(EnumValue!(A, i))~ ": return \""~EnumName!(A)~"\";\n"; } else { const char[] BuildOneCase = "case "~ToString!(EnumValue!(A, i))~ ": return \""~EnumName!(A)~"("~ToString!(EnumValue!(A, i))~")\";\n"; } } template BuildEnumCases(char[] A, int i, bool full) { static if (SplitChomp!(A, ",").Rest.length == 0) { const char[] BuildEnumCases = BuildOneCase!(A, EnumValue!(A, i), full); } else { const char[] BuildEnumCases = BuildOneCase!(SplitChomp!(A, ",").First, EnumValue!(A, i), full) ~ BuildEnumCases!(SplitChomp!(A, ",").Rest, EnumValue!(A, i)+1,full); } } template BuildEnumSwitch(char[] A, int i, bool full) { const char[] BuildEnumSwitch = "switch(x) {"~ BuildEnumCases!(A, i, full) ~ "default: "~ " throw new Exception(\"enumeration out of range\");" "}"; } template BuildOneApply(char[] A, int i) { const char[] BuildOneApply = "i="~ToString!(EnumValue!(A, i))~";"~ " rv=dg(i); if (rv) return rv;"; } template BuildEnumApply(char[] A, int i) { static if (SplitChomp!(A, ",").Rest.length == 0) { const char[] BuildEnumApply = BuildOneApply!(A, EnumValue!(A, i)); } else { const char[] BuildEnumApply = BuildOneApply!(SplitChomp!(A, ",").First, EnumValue!(A, i))~ BuildEnumApply!(SplitChomp!(A, ",").Rest, EnumValue!(A, i)+1); } } struct Enum(char[] A) { mixin(BuildEnums!(A, 0)); static char[] toString(int x) { mixin(BuildEnumSwitch!(A, 0, false)); } static char[] toFullString(int x) { mixin(BuildEnumSwitch!(A, 0, true)); } int opApply(int delegate(inout int) dg) { int i, rv; mixin(BuildEnumApply!(A, 0)); return 0; } } int main(char[][] args) { alias Enum!("start, middle, end=10, ps, pps,") PState; int s = PState.start; int m = PState.middle; writefln("s is %s, with name %s %s\n", s, PState.toString(s), PState.toFullString(s)); writefln("m is %s, with name %s %s\n", m, PState.toString(m), PState.toFullString(m)); PState P; foreach(v; P) { writefln("Enum %s has name=%s and %s", v, PState.toString(v), PState.toFullString(v)); } return 0; }
Feb 16 2007
== Quote from renoX (renosky free.fr)'s articleKevin Bealer Wrote:...- why do Enum start the values at 1 instead of 0? IMHO it should follow the way 'enum' works otherwise developers may be confused.You're right -- this should match the default enum{} behavior. I actually did it this way as a kind of shorthand for myself. When I write an enum {} I usually add a starting value with a name like "e_None" or something. You could think of it as comparable to the floating point NaN. If I don't set an enumerated type, I want it to have an out-of-bound value.- why the name getString instead of the usual toString name?That could be changed too; I think of toString() as an object method, and when adding a static method, I figured I should use a different name to avoid conflicting with the method. I didn't really check whether there is a real conflict on this. There is kind of a strangeness with the way I defined this in that you creating instances of the Enum!(...) struct is not useful. The type is only really interesting for its static properties.- I'm not sure if it's useful to 'optimise' compilation time, but there were afew call to SplitChomp that can be replaced by SplitFirst.- toFullString which returns "<enum name>(<enum value>)" can be useful toomaybe, I wanted to do a concatenation reusing toString to avoid duplicating the code, but I didn't manage to, so I added a parameter instead.The code below has the modifications. I wonder if the reflective enums could be integrated into a library, I thinkthat it would be useful.The only downside of this version is that the developer must use int instead ofa separate type.. This is probably fixable. These modifications make sense. I'm thinking I can write a much smaller and more straightforward version with 1.006.renoX import std.stdio; import std.metastrings; template Find(char[] A, char[] B) { static if (A.length < B.length) { const int Find = -1; } else static if (A[0..B.length] == B) { const int Find = 0; } else static if (-1 == Find!(A[1..$], B)) { const int Find = -1; } else { const int Find = 1 + Find!(A[1..$], B); } } template SplitFirst(char[] A, char[] B) { const int Location = Find!(A, B); static if (Location == -1) { const char[] First = A; const char[] Rest = ""; } else { const char[] First = A[0..Location]; const char[] Rest = A[Location+B.length..$]; } } template ChompSpaces(char[] A) { static if (A.length) { static if (A[0] == ' ') { alias ChompSpaces!(A[1..$]) ChompSpaces; } else static if (A[$-1] == ' ') { alias ChompSpaces!(A[0..$-1]) ChompSpaces; } else { alias A ChompSpaces; } } else { const char[] ChompSpaces = ""; } } template SplitChomp(char[] A, char[] B) { alias ChompSpaces!(SplitFirst!(A, B).First) First; alias ChompSpaces!(SplitFirst!(A, B).Rest) Rest; } template EnumName(char[] A) { alias SplitChomp!(SplitFirst!(A, ",").First, "=").First EnumName; } template EnumAssign(char[] A) { alias SplitChomp!(SplitFirst!(A, ",").First, "=").Rest EnumAssign; } template EnumValue(char[] A, int i) { static if (EnumAssign!(A) == "") { const int EnumValue = i; } else { const int EnumValue = mixin(ParseInteger!(EnumAssign!(A)).value); } } template BuildOneEnum(char[] A, int i) { const char[] BuildOneEnum = "const int "~EnumName!(A)~" = "~ ToString!(EnumValue!(A, i))~";\n"; } template BuildEnums(char[] A, int i) { static if (SplitChomp!(A, ",").Rest.length == 0) { const char[] BuildEnums = BuildOneEnum!(A, EnumValue!(A, i)); } else { const char[] BuildEnums = BuildOneEnum!(SplitChomp!(A, ",").First, EnumValue!(A, i)) ~ BuildEnums!(SplitChomp!(A, ",").Rest, EnumValue!(A, i)+1); } } template BuildOneCase(char[] A, int i, bool full) { static if (!full) { const char[] BuildOneCase = "case "~ToString!(EnumValue!(A, i))~ ": return \""~EnumName!(A)~"\";\n"; } else { const char[] BuildOneCase = "case "~ToString!(EnumValue!(A, i))~ ": return \""~EnumName!(A)~"("~ToString!(EnumValue!(A, i))~")\";\n"; } } template BuildEnumCases(char[] A, int i, bool full) { static if (SplitChomp!(A, ",").Rest.length == 0) { const char[] BuildEnumCases = BuildOneCase!(A, EnumValue!(A, i), full); } else { const char[] BuildEnumCases = BuildOneCase!(SplitChomp!(A, ",").First, EnumValue!(A, i), full) ~ BuildEnumCases!(SplitChomp!(A, ",").Rest, EnumValue!(A, i)+1,full); } } template BuildEnumSwitch(char[] A, int i, bool full) { const char[] BuildEnumSwitch = "switch(x) {"~ BuildEnumCases!(A, i, full) ~ "default: "~ " throw new Exception(\"enumeration out of range\");" "}"; } template BuildOneApply(char[] A, int i) { const char[] BuildOneApply = "i="~ToString!(EnumValue!(A, i))~";"~ " rv=dg(i); if (rv) return rv;"; } template BuildEnumApply(char[] A, int i) { static if (SplitChomp!(A, ",").Rest.length == 0) { const char[] BuildEnumApply = BuildOneApply!(A, EnumValue!(A, i)); } else { const char[] BuildEnumApply = BuildOneApply!(SplitChomp!(A, ",").First, EnumValue!(A, i))~ BuildEnumApply!(SplitChomp!(A, ",").Rest, EnumValue!(A, i)+1); } } struct Enum(char[] A) { mixin(BuildEnums!(A, 0)); static char[] toString(int x) { mixin(BuildEnumSwitch!(A, 0, false)); } static char[] toFullString(int x) { mixin(BuildEnumSwitch!(A, 0, true)); } int opApply(int delegate(inout int) dg) { int i, rv; mixin(BuildEnumApply!(A, 0)); return 0; } } int main(char[][] args) { alias Enum!("start, middle, end=10, ps, pps,") PState; int s = PState.start; int m = PState.middle; writefln("s is %s, with name %s %s\n", s, PState.toString(s),PState.toFullString(s));writefln("m is %s, with name %s %s\n", m, PState.toString(m),PState.toFullString(m));PState P; foreach(v; P) { writefln("Enum %s has name=%s and %s", v, PState.toString(v),PState.toFullString(v));} return 0; }
Feb 16 2007
Kevin Bealer a écrit :== Quote from renoX (renosky free.fr)'s articleFrom my testing, it doesn't trigger a conflict, but I've only tested it inside one file.- why the name getString instead of the usual toString name?That could be changed too; I think of toString() as an object method, and when adding a static method, I figured I should use a different name to avoid conflicting with the method. I didn't really check whether there is a real conflict on this.There is kind of a strangeness with the way I defined this in that you creating instances of the Enum!(...) struct is not useful. The type is only really interesting for its static properties.About this I was wondering if it wouldn't be less strange to do a template like this: // expected usage: DefEnum!("enum ListEnumFoo {A,B=1};") template DefEnum(char[] def_enum) { mixin(def_enum); static toString(EnumType!(def_enum) x) { // function body similar to the inital get_String() } } Advantages: -The reflective enum type is really an enum type, so it acts like one. -No weird struct. -When(If) I can convince Walther that writef("foo %s",x) means really writef("foo "~x.toString()); we could write: writef("enum x value is %d and name is %s\n",x,x); and have the correct result, because toString(ListEnumFoo x) would hide toString(int x) {x being an enum variable from type ListEnumFoo). Disadvantage: No easy way to iterate among the enum values: we cannot do foreach(v; ListEnumFoo) because now ListEnumFoo is an enum not a struct.. And we cannot pass an enum type as a parameter, other it would be easy to define function 'keys' and 'values' (like for associative arrays), so we'd need a dummy parameter, i.e you wouldn't be able to write foreach (v; ListEnumFoo) {} and neither foreach (v; ListEnumFoo.values()) {} but foreach (v; ListEnumFoo.A.values()) {} could perhaps work. What do you think about this other way to do it? Regards, renoX
Feb 16 2007
renoX wrote:Kevin Bealer a écrit :Advantage: you would be able to reuse the code as a sub-set of something else like serialization or another language that uses the same enum syntax. Disadvantage: Code is harder to understand. Code runs slower. -Joel -Joel== Quote from renoX (renosky free.fr)'s articleFrom my testing, it doesn't trigger a conflict, but I've only tested it inside one file.- why the name getString instead of the usual toString name?That could be changed too; I think of toString() as an object method, and when adding a static method, I figured I should use a different name to avoid conflicting with the method. I didn't really check whether there is a real conflict on this.There is kind of a strangeness with the way I defined this in that you creating instances of the Enum!(...) struct is not useful. The type is only really interesting for its static properties.About this I was wondering if it wouldn't be less strange to do a template like this: // expected usage: DefEnum!("enum ListEnumFoo {A,B=1};")
Feb 16 2007
janderson wrote:renoX wrote:Correction: The interpreter code is harder to understand.Kevin Bealer a écrit :Advantage: you would be able to reuse the code as a sub-set of something else like serialization or another language that uses the same enum syntax. Disadvantage: Code is harder to understand. Code runs slower.== Quote from renoX (renosky free.fr)'s articleFrom my testing, it doesn't trigger a conflict, but I've only tested it inside one file.- why the name getString instead of the usual toString name?That could be changed too; I think of toString() as an object method, and when adding a static method, I figured I should use a different name to avoid conflicting with the method. I didn't really check whether there is a real conflict on this.There is kind of a strangeness with the way I defined this in that you creating instances of the Enum!(...) struct is not useful. The type is only really interesting for its static properties.About this I was wondering if it wouldn't be less strange to do a template like this: // expected usage: DefEnum!("enum ListEnumFoo {A,B=1};")-Joel -Joel
Feb 16 2007
janderson a écrit :janderson wrote:Thanks for the correction, your initial remark was puzzling me. That said why do you think the interpreter code is harder to understand? In the template, the part printing the enum value is exactly the same as before with just two added (very simple) templates one to get the enum typename, another to get the enum body. For some reason I've lost the code, I'll post it on Monday, but 90% of the code is still kevin bealer's code, so it doesn't look very different than before. Regards, renoXrenoX wrote:Correction: The interpreter code is harder to understand.Kevin Bealer a écrit :Advantage: you would be able to reuse the code as a sub-set of something else like serialization or another language that uses the same enum syntax. Disadvantage: Code is harder to understand. Code runs slower.== Quote from renoX (renosky free.fr)'s articleFrom my testing, it doesn't trigger a conflict, but I've only tested it inside one file.- why the name getString instead of the usual toString name?That could be changed too; I think of toString() as an object method, and when adding a static method, I figured I should use a different name to avoid conflicting with the method. I didn't really check whether there is a real conflict on this.There is kind of a strangeness with the way I defined this in that you creating instances of the Enum!(...) struct is not useful. The type is only really interesting for its static properties.About this I was wondering if it wouldn't be less strange to do a template like this: // expected usage: DefEnum!("enum ListEnumFoo {A,B=1};")-Joel -Joel
Feb 16 2007
renoX wrote:Kevin Bealer a écrit :I don't like the extra syntax -- in your example, the word 'enum' appears three times. I also like the ability to foreach() over an enum. But this has made me think of another idea that adds some of the "enum"-ness back to the struct. I'll post it if I can make it work. Kevin== Quote from renoX (renosky free.fr)'s articleFrom my testing, it doesn't trigger a conflict, but I've only tested it inside one file.- why the name getString instead of the usual toString name?That could be changed too; I think of toString() as an object method, and when adding a static method, I figured I should use a different name to avoid conflicting with the method. I didn't really check whether there is a real conflict on this.There is kind of a strangeness with the way I defined this in that you creating instances of the Enum!(...) struct is not useful. The type is only really interesting for its static properties.About this I was wondering if it wouldn't be less strange to do a template like this: // expected usage: DefEnum!("enum ListEnumFoo {A,B=1};") template DefEnum(char[] def_enum) { mixin(def_enum); static toString(EnumType!(def_enum) x) { // function body similar to the inital get_String() } } Advantages: -The reflective enum type is really an enum type, so it acts like one. -No weird struct. -When(If) I can convince Walther that writef("foo %s",x) means really writef("foo "~x.toString()); we could write: writef("enum x value is %d and name is %s\n",x,x); and have the correct result, because toString(ListEnumFoo x) would hide toString(int x) {x being an enum variable from type ListEnumFoo). Disadvantage: No easy way to iterate among the enum values: we cannot do foreach(v; ListEnumFoo) because now ListEnumFoo is an enum not a struct.. And we cannot pass an enum type as a parameter, other it would be easy to define function 'keys' and 'values' (like for associative arrays), so we'd need a dummy parameter, i.e you wouldn't be able to write foreach (v; ListEnumFoo) {} and neither foreach (v; ListEnumFoo.values()) {} but foreach (v; ListEnumFoo.A.values()) {} could perhaps work. What do you think about this other way to do it? Regards, renoX
Feb 16 2007
Kevin Bealer wrote:int main(char[][] args) { alias Enum!("start, middle, end=10, ps, pps") PState; int p = PState.middle; writefln("p is %s, with name %s\n", p, PState.getString(p)); PState P; foreach(v; P) { writefln("Enum %s has name=%s", v, PState.getString(v)); } return 0; }Nice one man! -Joel
Feb 16 2007
== Quote from janderson (askme me.com)'s articleKevin Bealer wrote:Thanks! I should say as a formality, I release all this into the public domain etc. Kevinint main(char[][] args) { alias Enum!("start, middle, end=10, ps, pps") PState; int p = PState.middle; writefln("p is %s, with name %s\n", p, PState.getString(p)); PState P; foreach(v; P) { writefln("Enum %s has name=%s", v, PState.getString(v)); } return 0; }Nice one man! -Joel
Feb 16 2007
Kevin Bealer Wrote:renoX wrote:This is easy to fix..Kevin Bealer a écrit :I don't like the extra syntax -- in your example, the word 'enum' appears three times.== Quote from renoX (renosky free.fr)'s articleFrom my testing, it doesn't trigger a conflict, but I've only tested it inside one file.- why the name getString instead of the usual toString name?That could be changed too; I think of toString() as an object method, and when adding a static method, I figured I should use a different name to avoid conflicting with the method. I didn't really check whether there is a real conflict on this.There is kind of a strangeness with the way I defined this in that you creating instances of the Enum!(...) struct is not useful. The type is only really interesting for its static properties.About this I was wondering if it wouldn't be less strange to do a template like this: // expected usage: DefEnum!("enum ListEnumFoo {A,B=1};") template DefEnum(char[] def_enum) { mixin(def_enum); static toString(EnumType!(def_enum) x) { // function body similar to the inital get_String() } } Advantages: -The reflective enum type is really an enum type, so it acts like one. -No weird struct. -When(If) I can convince Walther that writef("foo %s",x) means really writef("foo "~x.toString()); we could write: writef("enum x value is %d and name is %s\n",x,x); and have the correct result, because toString(ListEnumFoo x) would hide toString(int x) {x being an enum variable from type ListEnumFoo). Disadvantage: No easy way to iterate among the enum values: we cannot do foreach(v; ListEnumFoo) because now ListEnumFoo is an enum not a struct.. And we cannot pass an enum type as a parameter, other it would be easy to define function 'keys' and 'values' (like for associative arrays), so we'd need a dummy parameter, i.e you wouldn't be able to write foreach (v; ListEnumFoo) {} and neither foreach (v; ListEnumFoo.values()) {} but foreach (v; ListEnumFoo.A.values()) {} could perhaps work. What do you think about this other way to do it? Regards, renoXI also like the ability to foreach() over an enum. But this has made me think of another idea that adds some of the "enum"-ness back to the struct. I'll post it if I can make it work. KevinHow about we discuss our usecase first, then we could work on the API for declaration, use, print and foreach. Given our initial work as a basis, the implementation then should be easy either using templates or compile-time functions (I've seen your implementation and I must admit that I'm a bit disappointed that it doesn't look that much better than the one which used templates). I'll open a new thread so that the discussion about the design is seen by everyone, hopefully some will contribute. renoX
Feb 19 2007
renoX wrote:Kevin Bealer Wrote:Okay. A lot of the awkwardness in the code for the second one is due to limitations in the current CTFE. Things like char[][], functions like split(), format(), replace(), and so on from std.strings, would have made the code much simpler. I think in all these cases it is a matter of time. It's quite amazing that the CTFE stuff was added to DMD as quickly as it was but it has a few limits yet and its amazing how much of the language and library even a simple case like this uses. KevinrenoX wrote:This is easy to fix..Kevin Bealer a écrit :I don't like the extra syntax -- in your example, the word 'enum' appears three times.== Quote from renoX (renosky free.fr)'s articleFrom my testing, it doesn't trigger a conflict, but I've only tested it inside one file.- why the name getString instead of the usual toString name?That could be changed too; I think of toString() as an object method, and when adding a static method, I figured I should use a different name to avoid conflicting with the method. I didn't really check whether there is a real conflict on this.There is kind of a strangeness with the way I defined this in that you creating instances of the Enum!(...) struct is not useful. The type is only really interesting for its static properties.About this I was wondering if it wouldn't be less strange to do a template like this: // expected usage: DefEnum!("enum ListEnumFoo {A,B=1};") template DefEnum(char[] def_enum) { mixin(def_enum); static toString(EnumType!(def_enum) x) { // function body similar to the inital get_String() } } Advantages: -The reflective enum type is really an enum type, so it acts like one. -No weird struct. -When(If) I can convince Walther that writef("foo %s",x) means really writef("foo "~x.toString()); we could write: writef("enum x value is %d and name is %s\n",x,x); and have the correct result, because toString(ListEnumFoo x) would hide toString(int x) {x being an enum variable from type ListEnumFoo). Disadvantage: No easy way to iterate among the enum values: we cannot do foreach(v; ListEnumFoo) because now ListEnumFoo is an enum not a struct.. And we cannot pass an enum type as a parameter, other it would be easy to define function 'keys' and 'values' (like for associative arrays), so we'd need a dummy parameter, i.e you wouldn't be able to write foreach (v; ListEnumFoo) {} and neither foreach (v; ListEnumFoo.values()) {} but foreach (v; ListEnumFoo.A.values()) {} could perhaps work. What do you think about this other way to do it? Regards, renoXI also like the ability to foreach() over an enum. But this has made me think of another idea that adds some of the "enum"-ness back to the struct. I'll post it if I can make it work. KevinHow about we discuss our usecase first, then we could work on the API for declaration, use, print and foreach. Given our initial work as a basis, the implementation then should be easy either using templates or compile-time functions (I've seen your implementation and I must admit that I'm a bit disappointed that it doesn't look that much better than the one which used templates). I'll open a new thread so that the discussion about the design is seen by everyone, hopefully some will contribute. renoX
Feb 19 2007