www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - How do I defeat the gratuitous qualification of alias members?

reply Chad Joan <chadjoan gmail.com> writes:
I'm writing a C wrapper and I have an enum:

enum SANE_Status
{
     SANE_STATUS_GOOD = 0,     /* everything A-OK */
     SANE_STATUS_UNSUPPORTED,  /* operation is not supported */
     SANE_STATUS_CANCELLED,    /* operation was cancelled */
     ...
}

Now, what I really hate to see is this:

if ( status == SANE_Status.SANE_STATUS_GOOD )
     doSomething();

This is incompatible with how the C code would be written.  Even worse: 
so much repetitive noise!  It gets worse yet when the enum members are 
arguments to functions and there are more than one of them.

I understand that I could also write code this way:

alias int SANE_Status;
enum : SANE_Status
{
     SANE_STATUS_GOOD = 0,     /* everything A-OK */
     SANE_STATUS_UNSUPPORTED,  /* operation is not supported */
     SANE_STATUS_CANCELLED,    /* operation was cancelled */
     ...
}

This gives a more C-like behavior.  However, I no longer have an enum 
type that is distinct from 'int' and, more importantly, I can't use 
"final switch(status) {...}" on this because typeof(status) isn't an 
enum.  It still allows me to write the final switch without compiler 
errors, but it won't enforce exhaustiveness.

I am having some luck with this:

--------------------------
enum SANE_Status
{
     SANE_STATUS_GOOD = 0,       /* everything A-OK */
     SANE_STATUS_UNSUPPORTED,    /* operation is not supported */
     SANE_STATUS_CANCELLED,      /* operation was cancelled */
     SANE_STATUS_DEVICE_BUSY,    /* device is busy; try again later */
     SANE_STATUS_INVAL,          /* data is invalid (includes no dev at 
open) */
     SANE_STATUS_EOF,            /* no more data available (end-of-file) */
     SANE_STATUS_JAMMED,         /* document feeder jammed */
     SANE_STATUS_NO_DOCS,        /* document feeder out of documents */
     SANE_STATUS_COVER_OPEN,     /* scanner cover is open */
     SANE_STATUS_IO_ERROR,       /* error during device I/O */
     SANE_STATUS_NO_MEM,         /* out of memory */
     SANE_STATUS_ACCESS_DENIED   /* access to resource has been denied */
}

alias SANE_Status.SANE_STATUS_GOOD SANE_STATUS_GOOD;
alias SANE_Status.SANE_STATUS_UNSUPPORTED SANE_STATUS_UNSUPPORTED;
alias SANE_Status.SANE_STATUS_CANCELLED SANE_STATUS_CANCELLED;
alias SANE_Status.SANE_STATUS_DEVICE_BUSY SANE_STATUS_DEVICE_BUSY;
alias SANE_Status.SANE_STATUS_INVAL SANE_STATUS_INVAL;
alias SANE_Status.SANE_STATUS_EOF SANE_STATUS_EOF;
alias SANE_Status.SANE_STATUS_JAMMED SANE_STATUS_JAMMED;
alias SANE_Status.SANE_STATUS_NO_DOCS SANE_STATUS_NO_DOCS;
alias SANE_Status.SANE_STATUS_COVER_OPEN SANE_STATUS_COVER_OPEN;
alias SANE_Status.SANE_STATUS_IO_ERROR SANE_STATUS_IO_ERROR;
alias SANE_Status.SANE_STATUS_NO_MEM SANE_STATUS_NO_MEM;
alias SANE_Status.SANE_STATUS_ACCESS_DENIED SANE_STATUS_ACCESS_DENIED;


void foo(SANE_Status s)
{
     final switch(s)
     {
         case SANE_STATUS_GOOD: writeln("Good!"); break;
         case SANE_STATUS_UNSUPPORTED: writeln("operation is not 
supported"); break;
         case SANE_STATUS_CANCELLED: writeln("operation was cancelled"); 
break;
         case SANE_STATUS_DEVICE_BUSY: writeln("device is busy; try 
again later"); break;
         case SANE_STATUS_INVAL: writeln("data is invalid (includes no 
dev at open)"); break;
         case SANE_STATUS_EOF: writeln("no more data available 
(end-of-file)"); break;
         case SANE_STATUS_JAMMED: writeln("document feeder jammed"); break;
         case SANE_STATUS_NO_DOCS: writeln("document feeder out of 
documents"); break;
         case SANE_STATUS_COVER_OPEN: writeln("scanner cover is open"); 
break;
         case SANE_STATUS_IO_ERROR: writeln("error during device I/O"); 
break;
         case SANE_STATUS_NO_MEM: writeln("out of memory"); break;
         case SANE_STATUS_ACCESS_DENIED: writeln("access to resource has 
been denied"); break;
     }

     writeln("foo works.");
}

int main(string[] args)
{
     SANE_Status s = SANE_STATUS_GOOD;
     if ( s == SANE_STATUS_GOOD )
         writeln("hi!");

     foo(SANE_STATUS_GOOD);

     return 0;
}

--------------------------

But I would probably need some nontrivial CTFE to generate the aliases.

There has to be a better way!

Please help.
Apr 04 2013
next sibling parent "Chris Cain" <clcain uncg.edu> writes:
On Friday, 5 April 2013 at 03:51:00 UTC, Chad Joan wrote:
 There has to be a better way!

 Please help.
Greetings, Try a with statement: http://dlang.org/statement.html#WithStatement Another thing people commonly do is omit the "SANE_STATUS_" prefix in the D declarations, instead using "SaneStatus." if desired So, with both, it could look something like this: ---- import std.stdio; enum SaneStatus { good = 0, unsupported } void main(string[] args) { auto status = SaneStatus.good; with(SaneStatus) { if(status == good) writeln("Great!"); } } ---- Hope this helps! :)
Apr 04 2013
prev sibling next sibling parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 04/04/2013 08:50 PM, Chad Joan wrote:

 There has to be a better way!
You seem to be looking for the (to me) only sensible use of 'with': :) enum SANE_Status { SANE_STATUS_GOOD = 0, SANE_STATUS_UNSUPPORTED, } void foo(SANE_Status s) { final switch (s) with (SANE_Status) { case SANE_STATUS_GOOD: break; case SANE_STATUS_UNSUPPORTED: break; } } void main() { foo(SANE_Status.SANE_STATUS_GOOD); } If acceptable for you, I also recommend dropping the SANE_STATUS_ prefix from the values: enum SANE_Status { GOOD = 0, UNSUPPORTED, } void foo(SANE_Status s) { final switch (s) with (SANE_Status) { case GOOD: break; case UNSUPPORTED: break; } } void main() { foo(SANE_Status.GOOD); } Further, camel-casing the type and the values: enum SaneStatus { good = 0, unsupported, } void foo(SaneStatus s) { final switch (s) with (SaneStatus) { case good: break; case unsupported: break; } } void main() { foo(SaneStatus.good); } Ali
Apr 04 2013
parent reply Chad Joan <chadjoan gmail.com> writes:
On 04/05/2013 01:23 AM, Ali Çehreli wrote:
 On 04/04/2013 08:50 PM, Chad Joan wrote:

  > There has to be a better way!

 You seem to be looking for the (to me) only sensible use of 'with': :)

 enum SANE_Status
 {
 SANE_STATUS_GOOD = 0,
 SANE_STATUS_UNSUPPORTED,
 }

 void foo(SANE_Status s)
 {
 final switch (s) with (SANE_Status) {
 case SANE_STATUS_GOOD: break;
 case SANE_STATUS_UNSUPPORTED: break;
 }
 }

 void main()
 {
 foo(SANE_Status.SANE_STATUS_GOOD);
 }

 If acceptable for you, I also recommend dropping the SANE_STATUS_ prefix
 from the values:

 enum SANE_Status
 {
 GOOD = 0,
 UNSUPPORTED,
 }

 void foo(SANE_Status s)
 {
 final switch (s) with (SANE_Status) {
 case GOOD: break;
 case UNSUPPORTED: break;
 }
 }

 void main()
 {
 foo(SANE_Status.GOOD);
 }

 Further, camel-casing the type and the values:

 enum SaneStatus
 {
 good = 0,
 unsupported,
 }

 void foo(SaneStatus s)
 {
 final switch (s) with (SaneStatus) {
 case good: break;
 case unsupported: break;
 }
 }

 void main()
 {
 foo(SaneStatus.good);
 }

 Ali
Hey Ali and Chris, I had forgotten about 'with'. I'll have to experiment with that. I still want to make sure that C code with the original C enums doesn't require a lot of twiddling to be ported. This means I may make a template to generate more D-like enums from C enums, or possibly at least a template to dequalify the D enums. It's always bugged me that D enums seem to prevent the separation of two distinct enum features: - Type safety. - Forced name qualification. Usually I find the former incredibly useful and the latter to be a hindrance. It's very inconsistent with how module scoping works: module qualification is not required unless there is an ambiguity. I wish enums worked the same way.
Apr 04 2013
parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Chad Joan:

 It's always bugged me that D enums seem to prevent the 
 separation of two distinct enum features:
 - Type safety.
 - Forced name qualification.

 Usually I find the former incredibly useful and the latter to 
 be a hindrance.  It's very inconsistent with how module scoping 
 works: module qualification is not required unless there is an 
 ambiguity.  I wish enums worked the same way.
I'd like modules to require qualifications on default, just like in Python :-) Bye, bearophile
Apr 05 2013
parent reply Chad Joan <chadjoan gmail.com> writes:
On 04/05/2013 06:30 AM, bearophile wrote:
 Chad Joan:

 It's always bugged me that D enums seem to prevent the separation of
 two distinct enum features:
 - Type safety.
 - Forced name qualification.

 Usually I find the former incredibly useful and the latter to be a
 hindrance. It's very inconsistent with how module scoping works:
 module qualification is not required unless there is an ambiguity. I
 wish enums worked the same way.
I'd like modules to require qualifications on default, just like in Python :-) Bye, bearophile
Hmmm, I don't remember python doing this. Do you mean like Java? So I would have to write code like this: --- import std.file, std.array, std.stdio; void main() { std.stdio.writeln( std.array.replace( cast(string)std.file.read("file.txt"),"\r\n","\n")); } --- instead of code like this: --- import std.file, std.array, std.stdio; void main() { writeln(replace(cast(string)read("file.txt"),"\r\n","\n")); } --- ???
Apr 05 2013
parent reply "bearophile" <bearophileHUGS lycos.com> writes:
Chad Joan:

 Hmmm, I don't remember python doing this.  Do you mean like 
 Java?
I meant Python. In Python when you write: import foo You have then to use: foo.bar() If you want to import just bar you have to use: from foo import bar There is also this syntax, used only in special situations, like when you are using the REPL: from foo import * That imports all the names of foo.
 So I would have to write code like this:
 ---
 import std.file, std.array, std.stdio;

 void main()
 {
     std.stdio.writeln(
         std.array.replace(
             cast(string)std.file.read("file.txt"),"\r\n","\n"));
 }
 ---
 instead of code like this:
 ---
 import std.file, std.array, std.stdio;

 void main()
 {
     writeln(replace(cast(string)read("file.txt"),"\r\n","\n"));
 }
 ---
 ???
See the solutions used in Python. But this stuff is long settled in D, so this discussion is now academic :-) Bye, bearophile
Apr 05 2013
parent Chad Joan <chadjoan gmail.com> writes:
On 04/05/2013 01:48 PM, bearophile wrote:
 Chad Joan:

 Hmmm, I don't remember python doing this. Do you mean like Java?
See the solutions used in Python. But this stuff is long settled in D, so this discussion is now academic :-) Bye, bearophile
I skimmed some of the Python docs on this and it seems reasonable. In Python I'd get my shorter example by doing something like: from std.stdio import * Which would put all of the std.stdio module's symbols into the current module's lookup table. I probably used this a lot back when I used Python. I also noticed something in there that I wish I could do in D: from sound.effects import echo echo.echofilter(input, output, delay=0.7, atten=4) I think this would be analogous to the following D code, if it worked: import std; stdio.writeln("Hello world!"); That would be nice. D seems to force your symbol qualifications to be either fully qualified or not qualified at all, which is not always the best solution :/ If D can somehow do partially-qualified module names, then please teach me.
Apr 05 2013
prev sibling parent reply Chad Joan <chadjoan gmail.com> writes:
On 04/04/2013 11:50 PM, Chad Joan wrote:
...

FWIW, I was able to make a template to allow me to do what I want:

-------------------
mixin template dequalifyEnumMembers(theEnum, members...)
{
     static if ( members.length > 0 )
     {
         mixin("alias "~theEnum.stringof~"."~members[0]
             ~" "~members[0]~";");
         mixin dequalifyEnumMembers!(theEnum, members[1..$]);
     }
}

mixin template dequalifyEnum(theEnum) if (is(theEnum == enum))
{
     mixin dequalifyEnumMembers!(theEnum, __traits(allMembers, theEnum));
}
-------------------

Usage:

-------------------
mixin dequalifyEnum!MyEnum;
enum MyEnum
{
     Pliers,
     Forceps,
     Picks,
}

void foo(MyEnum m)
{
     final switch(m)
     {
         // Comment one of these lines out to get the
         //   "not represented in enum" error.
         case Pliers:  writeln("Pliers");  break;
         case Forceps: writeln("Forceps"); break;
         case Picks:   writeln("Picks");   break;
     }
}

void main()
{
     foo(Picks);
}
-------------------

I still can't escape the feeling that this is a hack to work around 
limitations of the language or lack of knowledge.
Apr 04 2013
next sibling parent reply "Tobias Pankrath" <tobias pankrath.net> writes:
On Friday, 5 April 2013 at 06:20:05 UTC, Chad Joan wrote:

 I still can't escape the feeling that this is a hack to work 
 around limitations of the language or lack of knowledge.
The only hack around a language limitation I see in this thread is the use of prefixes to distinguish between different enums. Your comparison between enum and modules is misplaced. It's better to compare enums with structs and classes. --- module A; struct Foo { enum name = "foo"; } -- module B; import A; void main() { writeln(name); } // don't think that should work --- And it shouldn't work for enums, too. Imports should only pull top level names into a scope.
Apr 04 2013
parent reply Chad Joan <chadjoan gmail.com> writes:
On 04/05/2013 02:50 AM, Tobias Pankrath wrote:
 On Friday, 5 April 2013 at 06:20:05 UTC, Chad Joan wrote:

 I still can't escape the feeling that this is a hack to work around
 limitations of the language or lack of knowledge.
The only hack around a language limitation I see in this thread is the use of prefixes to distinguish between different enums. Your comparison between enum and modules is misplaced. It's better to compare enums with structs and classes. --- module A; struct Foo { enum name = "foo"; } -- module B; import A; void main() { writeln(name); } // don't think that should work --- And it shouldn't work for enums, too. Imports should only pull top level names into a scope.
I am unconvinced: Enums are analogous to a list of constants, not to a struct or a class. Structs and classes have instances. Enums do not have instances. Whenever I see --- enum Foo { X, Y } --- I think of it as being similar to --- const X = 0; const Y = 1; --- with the primary differences of type safety and manifest-constant-ness (the enum acts more like a preprocessor define in C and creates immediate values rather than link-time symbols).
Apr 05 2013
parent reply Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 4/5/13, Chad Joan <chadjoan gmail.com> wrote:
 Enums do not have instances.
Sure they do. enum Foo { X, Y } void test(Foo foo) { } void main() { Foo foo = Foo.Y; test(foo); }
Apr 05 2013
parent reply Chad Joan <chadjoan gmail.com> writes:
On 04/05/2013 01:18 PM, Andrej Mitrovic wrote:
 On 4/5/13, Chad Joan<chadjoan gmail.com>  wrote:
 Enums do not have instances.
Sure they do. enum Foo { X, Y } void test(Foo foo) { } void main() { Foo foo = Foo.Y; test(foo); }
Where's the instance? All I see is void test(int foo) { } void main() { int foo = 1; test(foo); } There is no stateful aggregation for me to see here.
Apr 05 2013
parent reply Chad Joan <chadjoan gmail.com> writes:
On 04/05/2013 01:48 PM, Chad Joan wrote:
 On 04/05/2013 01:18 PM, Andrej Mitrovic wrote:
 On 4/5/13, Chad Joan<chadjoan gmail.com> wrote:
 Enums do not have instances.
Sure they do. enum Foo { X, Y } void test(Foo foo) { } void main() { Foo foo = Foo.Y; test(foo); }
Where's the instance? All I see is void test(int foo) { } void main() { int foo = 1; test(foo); } There is no stateful aggregation for me to see here.
I can probably word this another way, since I might not have been entirely clear. Things like structs and classes occupy memory. Enums do not. Foo.Y expands to an immediate value and has no storage in the program's data segment. Enums place constraints on other things that do occupy memory: usually integers.
Apr 05 2013
parent reply "Tobias Pankrath" <tobias pankrath.net> writes:
On Friday, 5 April 2013 at 18:03:33 UTC, Chad Joan wrote:
 On 04/05/2013 01:48 PM, Chad Joan wrote:
 On 04/05/2013 01:18 PM, Andrej Mitrovic wrote:
 On 4/5/13, Chad Joan<chadjoan gmail.com> wrote:
 Enums do not have instances.
Sure they do. enum Foo { X, Y } void test(Foo foo) { } void main() { Foo foo = Foo.Y; test(foo); }
foo is the instance.
 I can probably word this another way, since I might not have 
 been entirely clear.

 Things like structs and classes occupy memory.  Enums do not.  
 Foo.Y expands to an immediate value and has no storage in the 
 program's data segment.  Enums place constraints on other 
 things that do occupy memory: usually integers.
foo does occupy memory. That foo is represented just like an integer does not change this. foo could be a struct or an array, too.
Apr 05 2013
parent Chad Joan <chadjoan gmail.com> writes:
On 04/05/2013 02:06 PM, Tobias Pankrath wrote:
 On Friday, 5 April 2013 at 18:03:33 UTC, Chad Joan wrote:
 On 04/05/2013 01:48 PM, Chad Joan wrote:
 On 04/05/2013 01:18 PM, Andrej Mitrovic wrote:
 On 4/5/13, Chad Joan<chadjoan gmail.com> wrote:
 Enums do not have instances.
Sure they do. enum Foo { X, Y } void test(Foo foo) { } void main() { Foo foo = Foo.Y; test(foo); }
foo is the instance.
 I can probably word this another way, since I might not have been
 entirely clear.

 Things like structs and classes occupy memory. Enums do not. Foo.Y
 expands to an immediate value and has no storage in the program's data
 segment. Enums place constraints on other things that do occupy
 memory: usually integers.
foo does occupy memory. That foo is represented just like an integer does not change this. foo could be a struct or an array, too.
At this point it is probably definitional. I would prefer to define enums as a kind of constraint over types, rather than as its own storable type. This is just much more useful to me. Usage-wise, I see enums doing two things: (1) Constraining function input/output to a named set of possible values, at compile-time. (2) Requiring constants to be qualified with a tag name. I think that both of these are useful in different situations. What I don't like is that they are tightly coupled. Usually I want (1). (1) can eliminate sources of bugs. That's awesome. Usually (2) is a hindrance to me. It causes a lot of line-wrapping and noise. The D compiler is good enough at finding ambiguities and erroring on them that I feel it is unnecessary in most situations (I'm sure there are exceptions). (2) is especially bad for flag sets that get ORed together. It's so clumsy that it discourages the use of enums. Thus, having (2) tightly coupled with (1) can undermine the benefits of (1). It runs against one of Walter's design goals: the easy thing should be the safe and correct thing. Right now the safe and correct thing requires extra noise and line wraps; it is not easier. I really just wish there was an easy way to pick-and-choose which attributes are desired for a given enum, based on usage scenario. I will probably be writing "mixin dequalifyEnum!SomeEnum;" next to a lot of my enums from now on. It's just another papercut, like all of the typedef'ing on structs in C.
Apr 05 2013
prev sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 4/5/13, Chad Joan <chadjoan gmail.com> wrote:
 On 04/04/2013 11:50 PM, Chad Joan wrote:
 ...

 FWIW, I was able to make a template to allow me to do what I want:
Yes, I have one too: https://github.com/AndrejMitrovic/minilib/blob/833ddfa2a914a0af139c043ed8a9b79be1ca2f7e/src/minilib/core/traits.d#L634
Apr 05 2013