Jump to page: 1 2
Thread overview
How do I defeat the gratuitous qualification of alias members?
Apr 05, 2013
Chad Joan
Apr 05, 2013
Chris Cain
Apr 05, 2013
Ali Çehreli
Apr 05, 2013
Chad Joan
Apr 05, 2013
bearophile
Apr 05, 2013
Chad Joan
Apr 05, 2013
bearophile
Apr 05, 2013
Chad Joan
Apr 05, 2013
Chad Joan
Apr 05, 2013
Tobias Pankrath
Apr 05, 2013
Chad Joan
Apr 05, 2013
Andrej Mitrovic
Apr 05, 2013
Chad Joan
Apr 05, 2013
Chad Joan
Apr 05, 2013
Tobias Pankrath
Apr 05, 2013
Chad Joan
Apr 05, 2013
Andrej Mitrovic
April 05, 2013
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.
April 05, 2013
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! :)
April 05, 2013
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

April 05, 2013
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.
April 05, 2013
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.
April 05, 2013
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.


April 05, 2013
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
April 05, 2013
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
April 05, 2013
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).

April 05, 2013
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"));
}
---
???


« First   ‹ Prev
1 2