www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Classes as enums in D?

reply Andrew LaChance <lachance.ak gmail.com> writes:
Hello,
D has intrigued me for a while, and I thought I would finally 
read up on it!  I've been reading "Programming in D" by Ali 
Çehreli and I've been thinking about how I can use the language 
in a side project I'm working on, porting it from java to D.  One 
of the uncommonly-used features of java that I like is how enums 
can be full classes (though I don't like that there's no option 
to use enums as e.g. regular ints).  This allows several 
benefits, such as the ability to use them in switch statements 
like regular enums, the full set of objects is known at compile 
time, all objects are immutable, it's impossible to accidentally 
or purposefully create new objects of that type, etc...

For example (in java), if I wanted to have an enum that describes 
all the white keys on a piano keyboard and have members that 
describe the number of half-steps to the next white key and to 
the previous white key, I can define an enum (the "id" or enum 
value is implicitly defined so it doesn't have to be explicitly 
written in the definition):

enum WhiteKey
{
     A(2,2),
     B(2,1),
     C(1,2),
     D(2,2),
     E(2,1),
     F(1,2),
     G(2,2);

     private final int halfStepsToNext;
     private final int halfStepsToPrevious;

     WhiteKey(int halfStepsPrevious, int halfStepsNext)
     {
         this.halfStepsToPrevious = halfStepsPrevious;
         this.halfStepsToNext = halfStepsNext;
     }
}

 From what I've read and seen, in D all enums have forced to 
integral types.  Is it possible to do the above in D and I have 
just missed it?  I can think of a few ways around it (such as 
statically create and define a bunch of WhiteKey structs, ...), 
but none are as clean as the above.  If this isn't something 
supported, is it on a roadmap of wanted features?

Thanks!  I'm looking forward to really getting to know the 
language.
Nov 29 2015
next sibling parent reply Rikki Cattermole <alphaglosined gmail.com> writes:
On 30/11/15 8:48 PM, Andrew LaChance wrote:
 Hello,
 D has intrigued me for a while, and I thought I would finally read up on
 it!  I've been reading "Programming in D" by Ali Çehreli and I've been
 thinking about how I can use the language in a side project I'm working
 on, porting it from java to D.  One of the uncommonly-used features of
 java that I like is how enums can be full classes (though I don't like
 that there's no option to use enums as e.g. regular ints).  This allows
 several benefits, such as the ability to use them in switch statements
 like regular enums, the full set of objects is known at compile time,
 all objects are immutable, it's impossible to accidentally or
 purposefully create new objects of that type, etc...

 For example (in java), if I wanted to have an enum that describes all
 the white keys on a piano keyboard and have members that describe the
 number of half-steps to the next white key and to the previous white
 key, I can define an enum (the "id" or enum value is implicitly defined
 so it doesn't have to be explicitly written in the definition):

 enum WhiteKey
 {
      A(2,2),
      B(2,1),
      C(1,2),
      D(2,2),
      E(2,1),
      F(1,2),
      G(2,2);

      private final int halfStepsToNext;
      private final int halfStepsToPrevious;

      WhiteKey(int halfStepsPrevious, int halfStepsNext)
      {
          this.halfStepsToPrevious = halfStepsPrevious;
          this.halfStepsToNext = halfStepsNext;
      }
 }

  From what I've read and seen, in D all enums have forced to integral
 types.  Is it possible to do the above in D and I have just missed it?
 I can think of a few ways around it (such as statically create and
 define a bunch of WhiteKey structs, ...), but none are as clean as the
 above.  If this isn't something supported, is it on a roadmap of wanted
 features?

 Thanks!  I'm looking forward to really getting to know the language.
enums don't have to be integral, but for performance reasons it is for the best. enum Foo : string { A = "a", B = "b", C = "d", ERROR = "What are you talking about?" } void main() { import std.stdio : writeln; Foo foo = Foo.ERROR; writeln(foo, " is: ", cast(string)foo); } Also you are welcome in #d on Freenode (IRC) if you are interesting in talking with the rest of us! Btw you probably want tuples (std.typecons : tuple) to emulate those values.
Nov 29 2015
parent reply Andrew LaChance <lachance.ak gmail.com> writes:
On Monday, 30 November 2015 at 07:54:49 UTC, Rikki Cattermole 
wrote:
 enums don't have to be integral, but for performance reasons it 
 is for the best.

 enum Foo : string {
     A = "a",
     B = "b",
     C = "d",
     ERROR = "What are you talking about?"
 }

 void main() {
 	import std.stdio : writeln;
 	Foo foo = Foo.ERROR;
 	writeln(foo, " is: ", cast(string)foo);
 }

 Also you are welcome in #d on Freenode (IRC) if you are 
 interesting in talking with the rest of us!
 Btw you probably want tuples (std.typecons : tuple) to emulate 
 those values.
Oh interesting. So you are saying I could have a struct WhiteKey {...} and then an enum that extends WhiteKey? You also mention Tuples, are you recommending the Tuple's type be (WhiteKey, int)? Thanks!
Nov 29 2015
next sibling parent Mike Parker <aldacron gmail.com> writes:
On Monday, 30 November 2015 at 07:58:43 UTC, Andrew LaChance 
wrote:

 Oh interesting.  So you are saying I could have a struct 
 WhiteKey {...} and then an enum that extends WhiteKey?
enums can't *extend* anything. You can do this: struct WhiteKeyS { immutable int halfStepsToPrevious; immutable int halfStepsToNext; } enum WhiteKey { A = WhiteKeyS(2, 2), B = WhiteKeyS(2, 1), C = WhiteKeyS(1, 2) } void main() { import std.stdio; writeln(WhiteKey.A.halfStepsToPrevious); }
Nov 30 2015
prev sibling parent Rikki Cattermole <alphaglosined gmail.com> writes:
On 30/11/15 8:58 PM, Andrew LaChance wrote:
 On Monday, 30 November 2015 at 07:54:49 UTC, Rikki Cattermole wrote:
 enums don't have to be integral, but for performance reasons it is for
 the best.

 enum Foo : string {
     A = "a",
     B = "b",
     C = "d",
     ERROR = "What are you talking about?"
 }

 void main() {
     import std.stdio : writeln;
     Foo foo = Foo.ERROR;
     writeln(foo, " is: ", cast(string)foo);
 }

 Also you are welcome in #d on Freenode (IRC) if you are interesting in
 talking with the rest of us!
 Btw you probably want tuples (std.typecons : tuple) to emulate those
 values.
Oh interesting. So you are saying I could have a struct WhiteKey {...} and then an enum that extends WhiteKey? You also mention Tuples, are you recommending the Tuple's type be (WhiteKey, int)? Thanks!
An enum does not extend other types. It specifies what the type of the value will be. It is a little like a map in that way. I would recommend that the tuple to be the value type of the enum. You could alias TypeTuple and use it directly and name the fields. All it is, is a struct.
Nov 30 2015
prev sibling parent reply Meta <jared771 gmail.com> writes:
On Monday, 30 November 2015 at 07:48:37 UTC, Andrew LaChance 
wrote:
 Hello,
 D has intrigued me for a while, and I thought I would finally 
 read up on it!  I've been reading "Programming in D" by Ali 
 Çehreli and I've been thinking about how I can use the language 
 in a side project I'm working on, porting it from java to D.  
 One of the uncommonly-used features of java that I like is how 
 enums can be full classes (though I don't like that there's no 
 option to use enums as e.g. regular ints).  This allows several 
 benefits, such as the ability to use them in switch statements 
 like regular enums, the full set of objects is known at compile 
 time, all objects are immutable, it's impossible to 
 accidentally or purposefully create new objects of that type, 
 etc...

 For example (in java), if I wanted to have an enum that 
 describes all the white keys on a piano keyboard and have 
 members that describe the number of half-steps to the next 
 white key and to the previous white key, I can define an enum 
 (the "id" or enum value is implicitly defined so it doesn't 
 have to be explicitly written in the definition):

 enum WhiteKey
 {
     A(2,2),
     B(2,1),
     C(1,2),
     D(2,2),
     E(2,1),
     F(1,2),
     G(2,2);

     private final int halfStepsToNext;
     private final int halfStepsToPrevious;

     WhiteKey(int halfStepsPrevious, int halfStepsNext)
     {
         this.halfStepsToPrevious = halfStepsPrevious;
         this.halfStepsToNext = halfStepsNext;
     }
 }

 From what I've read and seen, in D all enums have forced to 
 integral types.  Is it possible to do the above in D and I have 
 just missed it?  I can think of a few ways around it (such as 
 statically create and define a bunch of WhiteKey structs, ...), 
 but none are as clean as the above.  If this isn't something 
 supported, is it on a roadmap of wanted features?

 Thanks!  I'm looking forward to really getting to know the 
 language.
Yes and no. You can use arbitrary types for enums in D but a lot of the time you shouldn't when it involves types that are not Plain Old Data. A naive translation would be like this: class WhiteKey { private immutable int halfStepsToNext; private immutable int halfStepsToPrevious; enum { A = new WhiteKey(2, 2), B = new WhiteKey(2, 1), C = new WhiteKey(1, 2), D = new WhiteKey(2, 2), E = new WhiteKey(2, 1), F = new WhiteKey(1, 2), G = new WhiteKey(2, 2), } private this(int halfStepsToPrevious, int halfStepsToNext) { this.halfStepsToPrevious = halfStepsToPrevious; this.halfStepsToNext = halfStepsToNext; } } However, you do NOT want to do this, as everywhere you use WhiteKey's members, a new object will be created. For example: auto f = WhiteKey.A; auto n = WhiteKey.A; import std.stdio; writeln(&f, " ", &n); This will two different addresses, because a new object is being created each time. It's basically taking the expression `new Key(2, 2)` and copy-pasting it wherever you use WhiteKey.A. Java's enums are basically syntax sugar for this: class WhiteKey { private immutable int halfStepsToNext; private immutable int halfStepsToPrevious; public static WhiteKey A = new WhiteKey(2, 2); public static WhiteKey B = new WhiteKey(2, 1); public static WhiteKey C = new WhiteKey(1, 2); public static WhiteKey D = new WhiteKey(2, 2); public static WhiteKey E = new WhiteKey(2, 1); public static WhiteKey F = new WhiteKey(1, 2); public static WhiteKey G = new WhiteKey(2, 2); private this(int halfStepsToPrevious, int halfStepsToNext) { this.halfStepsToPrevious = halfStepsToPrevious; this.halfStepsToNext = halfStepsToNext; } } This doesn't quite work in D; you'd have to make each WhiteKey const (which is probably not a bad idea anyway if you're using it like an enum). However, it's better to just do this with plain old value-type structs. It's exactly the same as my previous code defining a WhiteKey class with an embedded enum, but using a struct instead of a class.
Nov 30 2015
next sibling parent reply Marc =?UTF-8?B?U2Now7x0eg==?= <schuetzm gmx.net> writes:
On Monday, 30 November 2015 at 08:08:20 UTC, Meta wrote:
 class WhiteKey
 {
 	private immutable int halfStepsToNext;
 	private immutable int halfStepsToPrevious;

 	enum
 	{
 		A = new WhiteKey(2, 2),
 		B = new WhiteKey(2, 1),
 		C = new WhiteKey(1, 2),
 		D = new WhiteKey(2, 2),
 		E = new WhiteKey(2, 1),
 		F = new WhiteKey(1, 2),
 		G = new WhiteKey(2, 2),
 	}
 	
 	private this(int halfStepsToPrevious, int halfStepsToNext)
 	{
 		this.halfStepsToPrevious = halfStepsToPrevious;
 		this.halfStepsToNext = halfStepsToNext;
 	}
 }

 However, you do NOT want to do this, as everywhere you use 
 WhiteKey's members, a new object will be created. For example:

 auto f = WhiteKey.A;
 auto n = WhiteKey.A;
 	
 import std.stdio;
 writeln(&f, " ", &n);
You're misinterpreting this: enum X { A = new Object, B = new Object, } void main() { import std.stdio; writeln(cast(void*) X.A); writeln(cast(void*) X.A); } # output: 470910 470910 You're print the address of `f` and `n` on the stack, not the reference they're pointing to. But it's true that enums of mutable _arrays_ do create a new instance every time they're used: enum X { A = [1,2,3], B = [4,5,6], } void main() { import std.stdio; writeln(X.A.ptr); writeln(X.A.ptr); } # output: 7FD887F0E000 7FD887F0E010
Nov 30 2015
parent Meta <jared771 gmail.com> writes:
On Monday, 30 November 2015 at 10:22:54 UTC, Marc Schütz wrote:
 You're misinterpreting this:

     enum X {
         A = new Object,
         B = new Object,
     }

     void main() {
         import std.stdio;
         writeln(cast(void*) X.A);
         writeln(cast(void*) X.A);
     }

 # output:
 470910
 470910

 You're print the address of `f` and `n` on the stack, not the 
 reference they're pointing to.

 But it's true that enums of mutable _arrays_ do create a new 
 instance every time they're used:

     enum X {
         A = [1,2,3],
         B = [4,5,6],
     }

     void main() {
         import std.stdio;
         writeln(X.A.ptr);
         writeln(X.A.ptr);
     }

 # output:
 7FD887F0E000
 7FD887F0E010
Whoops, you're right. I forgot you have to cast to a pointer for classes.
Nov 30 2015
prev sibling parent Andrew LaChance <lachance.ak gmail.com> writes:
On Monday, 30 November 2015 at 08:08:20 UTC, Meta wrote:
 This doesn't quite work in D; you'd have to make each WhiteKey 
 const (which is probably not a bad idea anyway if you're using 
 it like an enum). However, it's better to just do this with 
 plain old value-type structs. It's exactly the same as my 
 previous code defining a WhiteKey class with an embedded enum, 
 but using a struct instead of a class.
Thanks for the responses, everyone! Meta's is closest to one of the solutions I was thinking about and I'll probably end up going that way. It's not quite as pretty (as I would need to explicitly add the enum "id" to the struct and switch on that value instead of just the enum variable and I'll need to make sure no ids overlap), but it should work. I take it that not many others would be interested in the syntactic sugar of these like I am? :)
Nov 30 2015