www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Tuple enumeration without integers or strings

reply Rekel <paultjeadriaanse gmail.com> writes:
I seem to have hit a bit of a wall when comparing D enums to Java 
enums.
Of course the latter is just a fancy class, though it'd be nice 
to see partially equivalent usefulness regardless.

For example, as soon as non-integer, non-string values are given 
to enums things get messy for me when using switch cases, I 
haven't yet found a satisfying way of doing this.
Note the reason I'm using tuples is to somehow replicate the way 
java enumerals can contain several member variables, which tends 
to be very useful.

Something along the lines of the following is what i'd like to 
achieve;
 alias Direction = Tuple!(byte, "x", byte, "y");
 enum Wind {N = Direction(0, 1) ... etc}
 ...
 void some_function(Wind w) {
     switch (w) {
         case Wind.N:
             ... etc
             break;
         ... etc
         default:
             assert(0);
     }
 }
One thing that might have worked would have been an equivalent of java's "ordinal", though from what I've found D doesn't seem to have something equivalent to this? (I'd prefer not to have a seperate tuple member purely for ordinality, and dropping enumerals altogether also seems like a waste)
Jan 01 2021
next sibling parent reply Paul <paultjeadriaanse gmail.com> writes:
It seems w.to!string works in conjunction with Wind.N.stringof, 
though I wonder about the efficiency/wastefulness of this method.
Sadly this also leads to very funny behavior when some of the 
enums have the same value, as to!string(Enum) will yield the name 
of the first of these enums having the same values.

 alias Thing = Tuple!(int, int);
 enum Enum {
     A = Thing(0, 1),
     B = Thing(0, 2),
     C = Thing(0, 2)
 }
 void main(){
     Enum.C.to!string.writeln;
 }
For example, the code above will print 'B' (I'm slightly curious how & why std.conv uses the Enum name when converting to string, instead of using the value, especially since I didn't see this pitfall coming partly due to this) So this neither seems a satisfiable solution to me :/
Jan 01 2021
parent frame <frame86 live.com> writes:
On Saturday, 2 January 2021 at 00:57:48 UTC, Paul wrote:
 So this neither seems a satisfiable solution to me :/
Couldn't you just use Wind.N.hashOf and a custom format() expression for string-representation?
Jan 01 2021
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Friday, 1 January 2021 at 23:14:43 UTC, Rekel wrote:
 I seem to have hit a bit of a wall when comparing D enums to 
 Java enums.
 Of course the latter is just a fancy class, though it'd be nice 
 to see partially equivalent usefulness regardless.

 For example, as soon as non-integer, non-string values are 
 given to enums things get messy for me when using switch cases, 
 I haven't yet found a satisfying way of doing this.
D's switch statement only works on strings and integers. For more complex values, the easiest thing is to just use an if-else chain. If you really want to use a switch statement, you can do it by defining a function that maps each of your enum values to a unique integer; for example: enum Wind { ... } size_t index(Wind w) { if (w = Wind.N) return 0; // etc. } void someFunction(Wind w) { switch(w.index) { case Wind.N.index: // uses CTFE // ... break; // etc. default: assert(0); } }
Jan 01 2021
parent reply Paul <paultjeadriaanse gmail.com> writes:
On Saturday, 2 January 2021 at 03:20:29 UTC, Paul Backus wrote:
 D's switch statement only works on strings and integers. For 
 more complex values, the easiest thing is to just use an 
 if-else chain.

 If you really want to use a switch statement, you can do it by 
 defining a function that maps each of your enum values to a 
 unique integer; for example:
Im afraid that would still result in issues when duplicate enum vlues are at play right? (This issue would maybe warrant a compile time warning imho)
Jan 02 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Saturday, 2 January 2021 at 21:41:34 UTC, Paul wrote:
 On Saturday, 2 January 2021 at 03:20:29 UTC, Paul Backus wrote:
 D's switch statement only works on strings and integers. For 
 more complex values, the easiest thing is to just use an 
 if-else chain.

 If you really want to use a switch statement, you can do it by 
 defining a function that maps each of your enum values to a 
 unique integer; for example:
Im afraid that would still result in issues when duplicate enum vlues are at play right?
Yes, but this will be true of any approach you choose. If two enum members have exactly the same value, there is no way to distinguish between them, either at compile time or at runtime.
Jan 02 2021
parent reply Paul <paultjeadriaanse gmail.com> writes:
On Saturday, 2 January 2021 at 21:48:04 UTC, Paul Backus wrote:
 Yes, but this will be true of any approach you choose. If two 
 enum members have exactly the same value, there is no way to 
 distinguish between them, either at compile time or at runtime.
Oh I see, thanks! A bit of a bummer as I guess that means you're pretty much required to use an additional seperate structure like an array or map/associative array, the latter making the use of an enum instead of string names slightly pointless in this scenario, thank you nontheless 😅.
Jan 02 2021
parent reply frame <frame86 live.com> writes:
On Sunday, 3 January 2021 at 01:15:56 UTC, Paul wrote:
 On Saturday, 2 January 2021 at 21:48:04 UTC, Paul Backus wrote:
 Yes, but this will be true of any approach you choose. If two 
 enum members have exactly the same value, there is no way to 
 distinguish between them, either at compile time or at runtime.
Oh I see, thanks! A bit of a bummer as I guess that means you're pretty much required to use an additional seperate structure like an array or map/associative array, the latter making the use of an enum instead of string names slightly pointless in this scenario, thank you nontheless 😅.
Besides the problem with equal values, what's wrong with that: alias Thing = Tuple!(int, int); enum Wind { A = Thing(0, 1), B = Thing(0, 2), C = Thing(0, 2) } void some_function(Wind w) { switch (w.hashOf) { case Wind.B.hashOf: break; default: assert(0); } } void main() { some_function(Wind.B); writefln("%d%d", Wind.C.expand); }
Jan 02 2021
parent reply Paul <paultjeadriaanse gmail.com> writes:
On Sunday, 3 January 2021 at 02:17:43 UTC, frame wrote:
 Besides the problem with equal values, what's wrong with that:

 alias Thing = Tuple!(int, int);
 enum Wind {
     A = Thing(0, 1),
     B = Thing(0, 2),
     C = Thing(0, 2)
 }

 void some_function(Wind w) {
     switch (w.hashOf) {
     case Wind.B.hashOf:
         break;

     default:
         assert(0);
     }
 }

 void main() {
     some_function(Wind.B);
     writefln("%d%d", Wind.C.expand);
 }
I haven't used hashOf before, though assuming no equal values, which I generally wouldn't do, I take it this is reliable? I haven't tried it before, and I dont know how to effectively compare it to using 'switch(w.to!string)' & 'case Wind.B.stringof' (regarding speed/reliability).
Jan 02 2021
parent reply Paul Backus <snarwin gmail.com> writes:
On Sunday, 3 January 2021 at 02:41:12 UTC, Paul wrote:
 I haven't used hashOf before, though assuming no  equal values, 
 which I generally wouldn't do, I take it this is reliable? I 
 haven't tried it before, and I dont know how to effectively 
 compare it to using 'switch(w.to!string)' & 'case 
 Wind.B.stringof' (regarding speed/reliability).
hashOf is not guaranteed to produce unique values, so I would not advise using it here. to!string is expensive both at compile time (requires a lot of template instantiations) and runtime (needs to allocate memory for the string). It will work if you need to hack something together quickly, but I would not recommend it for "serious" code.
Jan 02 2021
parent reply frame <frame86 live.com> writes:
On Sunday, 3 January 2021 at 04:16:20 UTC, Paul Backus wrote:
 On Sunday, 3 January 2021 at 02:41:12 UTC, Paul wrote:
 hashOf is not guaranteed to produce unique values, so I would 
 not advise using it here.
Of course it's a hashing method but also internally used for comparison and good enough, at least in that example code. By using an enum I don't assume there will be that much members to take care of anyway. The hash is also generated at compile time.
Jan 02 2021
parent reply Paul <paultjeadriaanse gmail.com> writes:
On Sunday, 3 January 2021 at 06:05:48 UTC, frame wrote:
 The hash is also generated at compile time.
Is there an easy way for me to know when code is assessed / generated at compile time? For example, is indexing a constant compile time array compile time or run time? Or how about functions? The hashOf documentation does not seem to hint to being done at compile time.
Jan 03 2021
parent Mike Parker <aldacron gmail.com> writes:
On Sunday, 3 January 2021 at 13:36:57 UTC, Paul wrote:

 Is there an easy way for me to know when code is assessed / 
 generated at compile time?
 For example, is indexing a constant compile time array compile 
 time or run time?
 Or how about functions? The hashOf documentation does not seem 
 to hint to being done at compile time.
It's not what you do, but the context in which you do it. If a function must be evaluated at compile time, it will be. ``` string tellme() { if(__ctfe) return "Compile Time"; else return "Run time"; } void main() { writeln(tellme()); pragma(msg, tellme()); writeln(tellme()); } ``` Since the msg pragma is a compile-time construct, tellme *must* be evaluated at compile time. The calls to writeln are just normal function calls, therefore the calls to tellme happen at runtime. So any context where you replace a compile-time constant with a function call, you'll have CTFE (as long as it's possible [1]). It's common to use enum to force CTFE: enum s = tellme(); [1] https://dlang.org/spec/function.html#interpretation
Jan 03 2021