April 10, 2014
On Tuesday, 8 April 2014 at 19:08:45 UTC, Andrei Alexandrescu wrote:
> (moving http://goo.gl/ZISWwN to this group)
>
> ...
>
>
> Andreiu

Honestly, my biggest issues with enums are name-qualification followed by stringizing.

When I say name-qualification, I mean this:
File myFile;
myFile.open("foobar.txt",
    FileAccess.READ |
    FileAccess.WRITE );

Things get long and start to wrap due to enums always requiring full name qualification.  This is a mild example; it gets worse pretty easily.

I'd much rather write:
File myFile;
myFile.open("foobar.txt", READ | WRITE );

It should be possible, because the argument's context is of the (hypothetical) FileAccess enum type.  It should be unambiguous that I intend to use FileAccess's "READ" symbol there, and not some other symbol from module scope.

I find myself using a module like this: http://dpaste.dzfl.pl/9b97c02a26fd
And writing my enums like this:

mixin dequalifyEnum!FileAccess;
mixin toStringifyFlags!FileAccess;
enum FileAccess : int
{
    READ  = (1 << 0),
    WRITE = (1 << 1),
}

It is essentially a reimplementation of the X-macro pattern from C, but using CTFE+mixins to accomplish the same thing in D.

It expands to something like this:

alias FileAccess.READ READ;
alias FileAccess.WRITE WRITE;
import std.exception : assumeUnique;
string toString(FileAccess val)
{
    char[] result = "FileAccess{".dup;

    if ( val & FileAccess.READ )
        result ~= "READ|";

    if ( val & FileAccess.WRITE )
        result ~= "WRITE|";

    if ( result[$-1] == '|' )
        result[$-1] = '}';
    else
        result ~= "}";

    return assumeUnique(result);
}
enum FileAccess : int
{
    READ  = (1 << 0),
    WRITE = (1 << 1),
}

I feel that this approach is bad, because it relies on the library writer to attach the goodies to the enum.  It also exposes the enum symbols at module-level, which seems icky to many people.  I still find it worthwhile because I dislike line-wrapping and can always disambiguate symbols by prepending the module name.

I feel like the following should happen:

File myFile;

// Fine!
myFile.open("foobar.txt", READ | WRITE );

// Fine!
myFile.open("foobar.txt",
   FileAccess.READ |
   FileAccess.WRITE );

// ERROR: READ is not accessible in this scope.
int someInteger = READ;

// Maybe OK, depending on how strict you want to be.
// It is fairly explicit, after all.
int someInteger = FileAccess.READ;

// Fine!  The enum's scope is inferred from context.
FileAccess someInteger = READ | WRITE;
April 10, 2014
On 4/10/14, Chad Joan <chadjoan@gmail.com> wrote:
> I'd much rather write:
> File myFile;
> myFile.open("foobar.txt", READ | WRITE );
>
> It should be possible, because the argument's context is of the (hypothetical) FileAccess enum type.  It should be unambiguous that I intend to use FileAccess's "READ" symbol there, and not some other symbol from module scope.

There is a possible library workaround for this, e.g. a minimal example:

-----
struct FileAccess
{
    private alias This = typeof(this);

    This opBinary(string op)(This rhs)
    {
        mixin("return This(this.val " ~ op ~ " rhs.val);");
    }

private:
    int val;
}

enum : FileAccess
{
    READ  = FileAccess(1 << 0),
    WRITE = FileAccess(1 << 1)
}

void open(FileAccess access) { }

void main()
{
    open(READ | WRITE);
}
-----
April 10, 2014
Chad Joan:

I am not yet following this thread, but:

> myFile.open("foobar.txt",
>     FileAccess.READ |
>     FileAccess.WRITE );

(I use enum filds to be lowercase, as it's common in D):

with(FileAccess)
    myFile.open("foobar.txt", read | write);

Bye,
bearophile
April 10, 2014
On Thursday, 10 April 2014 at 04:47:31 UTC, Walter Bright wrote:
> It makes perfect sense if you think of an enum as an integral type, some values of which have names, as in the "Color" example I posted earlier.

I do not wish to think of enums in this way. Your use-case is better solved with Typedef and a struct.
April 10, 2014
On Thursday, 10 April 2014 at 14:37:37 UTC, Meta wrote:
> On Thursday, 10 April 2014 at 04:47:31 UTC, Walter Bright wrote:
>> It makes perfect sense if you think of an enum as an integral type, some values of which have names, as in the "Color" example I posted earlier.
>
> I do not wish to think of enums in this way. Your use-case is better solved with Typedef and a struct.

Me neither. An enumeration should exactly do this: enumerate each of it's possible values. If you like to do arithmetics on it, it's something different. Especially flags and colors are NOT enumerated - you give only some names for some edge cases. So why not call it such: "named examples" and make it a differnt type, where arithmetic is allowed and "final switch" is not (like for int)?
Especially this is what is really annoing, and a "default" case is not even allowed in a final switch - so it should also not be possible to construct illegal values other than by explicit unsave operations (like cast).
April 10, 2014
On 4/9/2014 1:58 PM, Nick Sabalausky wrote:
> Tooling is certainly very important, but until someone comes up with a
> substitute for "programming languages" that actually *works well* as a
> *complete* substitute (decades of attempts, still zero successes), then unlike
> tooling, the language is still the one thing that's absolutely *mandatory*.

Yeah, I've seen the "programming without programming" tools come and go over the decades. I'm not holding my breath.

To me, they always seem like "learn without effort" and "get fit without exercise" pitches.

April 10, 2014
On 4/9/14, 9:47 PM, Walter Bright wrote:
> On 4/9/2014 5:39 PM, Jonathan M Davis wrote:
>> On Wednesday, April 09, 2014 14:14:21 Andrei Alexandrescu wrote:
>>> One wants to call a function taking such an enum. -- Andrei
>>
>> But it _isn't_ going to be a valid enum value. IMHO, a function which is
>> taking or-ed flags where those flags are enums needs to take uint or
>> ulong or
>> whatever the base type of the enum is and _not_ the enum type.
>
> It makes perfect sense if you think of an enum as an integral type, some
> values of which have names, as in the "Color" example I posted earlier.

Problem is you think of it like that in a subset of cases only. FWIW I wouldn't have advocated for final switch if that was the fostered view. -- Andrei
April 10, 2014
On Thursday, 10 April 2014 at 07:19:43 UTC, Andrej Mitrovic wrote:
> On 4/8/14, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> wrote:
>> There are several questions to ask ourselves.
>
> There's another small issue with enums, you currently can't easily
> create new aliases to enums where you want to define aliases for both
> the enum name and the enum members.
>
> For example:
>
> -----
> void take(cairo_status_t e) { }
> cairo_status_t get() { return cairo_status_t.CAIRO_STATUS_SUCCESS; }
>
> // C-style enum
> enum cairo_status_t
> {
>     CAIRO_STATUS_SUCCESS,
>     CAIRO_STATUS_NO_MEMORY,
> }
>
> // D-style enum
> enum Status
> {
>     success = cairo_status_t.CAIRO_STATUS_SUCCESS,
>     noMemory = cairo_status_t.CAIRO_STATUS_NO_MEMORY
> }
>
> void main()
> {
>     take(Status.success);    // ok, compiles
>     Status x = get();   // error
> }
> -----
>
> You could use a struct wrapper with some alias this trickery, but this
> would be confusing compared to a normal enum, the documentation
> wouldn't look right, and things like EnumMembers and other enum traits
> would not work on this type.

We don't need a wrapper, we simply need to stop to port header with what you call C style enum. It is completely useless, and the whole problem you mention come from the fact that the header was translated to that.
April 11, 2014
On Wednesday, April 09, 2014 21:47:42 Walter Bright wrote:
> On 4/9/2014 5:39 PM, Jonathan M Davis wrote:
> > On Wednesday, April 09, 2014 14:14:21 Andrei Alexandrescu wrote:
> >> One wants to call a function taking such an enum. -- Andrei
> > 
> > But it _isn't_ going to be a valid enum value. IMHO, a function which is taking or-ed flags where those flags are enums needs to take uint or ulong or whatever the base type of the enum is and _not_ the enum type.
> 
> It makes perfect sense if you think of an enum as an integral type, some values of which have names, as in the "Color" example I posted earlier.

If you can't reasonably rely on a variable of an enum type being on of the enum's enumerated values, then final switch makes no sense whatsoever. Since it has no default case, you'd have to first check that the value was a valid enum value if you wanted the final switch to work to not hit the assert(0) or whatever it is that happens when you give a final switch a value that it doesn't have a case for.

For final switch to work correctly, you must be able to rely on variables of an enum type being one of the values enumerated in the enum, which means that the language should be restricting what you can set an enum variable's value to to valid enum values (except when casting) - and it pretty much only does that right now on direct assignment. It's trivial to append to  an enum if it's an array or do arithmetic on it if it's an arithmetic type. And that means that it's trivial to break final switch.

In addition , there's plenty of existing code that won't work if you give it a value that's not one of the enum's enumerated values - e.g. std.conv.to will choke when trying to convert an enum value if it's not one of the enum's enumerated values.

If you just want to give a list of values names, then you can use manifest constants rather than an enumeration. IMHO, enumerations only make sense when you're enumerating _all_ of the values for that enum. And if that's the case, the language shouldn't allow enum variables to be set to invalid enum values without casting. The current situation is highly bug-prone. And what you're suggesting would require that most code that deals with enums protect itself against being given enum values that weren't enumerated by the enum, which I'm willing to bet pretty much no one does.

- Jonathan M Davis
April 11, 2014
On 4/10/2014 10:29 PM, Jonathan M Davis wrote:
> If you just want to give a list of values names, then you can use manifest
> constants rather than an enumeration.

You could, but then you'd lose the self-documenting aspect of static type checking. See the "Color" example.

    alias int Color;

doesn't offer any protection from a Color value getting mixed up with a file mode mask.