June 24, 2018
On 6/24/18 11:46 AM, Per Nordlöw wrote:
> On Friday, 22 June 2018 at 00:50:05 UTC, Steven Schveighoffer wrote:
>> The sucky thing is, the compiler is *already* doing a sort on the items in the switch, and *already* doing the duplicate check. It would be cool to be able to leverage this mechanism to avoid the library solution, but I don't know how we can do that, as the semantics for switch are well defined, and there's no other way to hook this builtin functionality.
> 
> I would like to see a new trait named, say, `primaryMembers` or `nonAliasMembers` that returns exactly what the switch needs. I believe this is motivated by the fact that this is a serious issue; Without the user notices, the use of enums combined with ..to!string or io sucks up more and more RAM in the compiler as new members are added.
> 
> If enough (hundreds) of members are added you can get out of RAM, which is what happened to me. What do you think about that idea?
> 
> We should plot compilation time and memory usage with enumerator count so we can reason about the severity of this issue.

Hm... just had another thought.

Many times, the enum has no repeating members, so your mechanism just works. So just use that if it works!

static if(__traits(compiles, fastEnumToString(val)))
  return fastEnumToString(val);
else
  return slowEnumToString(val); // checks for duplicates

Should eliminate the issues, because it's not going to compile the slow version if the fast version can work.

-Steve
June 24, 2018
On Sunday, 24 June 2018 at 17:23:54 UTC, Steven Schveighoffer wrote:
> static if(__traits(compiles, fastEnumToString(val)))
>   return fastEnumToString(val);
> else
>   return slowEnumToString(val); // checks for duplicates
>
> Should eliminate the issues, because it's not going to compile the slow version if the fast version can work.
>
> -Steve

Yes, I thought about that too, but the problem is that std.conv.to is used in std.stdio and I don't want to remember to always to do

    writeln("Some text:", x.to!string);

instead of

    writeln("Some text:", x);

for some enum instance `x`.

I'm gonna hack up another solution

struct Enum(E)
if (is(E == enum))
{
    @property string toString() @safe pure nothrow @nogc
    {
        // fast implementation
    }
    E _enum;
    alias _enum this;
}

Further, it just struck me that we can generalize my fast solution to include enumerations with enumerator aliases that are defined directly after its original enumerator by checking with  a `static if` if the current enumerator value equals the previous then we skip it. I'm gonna post the solution here after some hacking.
June 24, 2018
On Sunday, 24 June 2018 at 21:47:14 UTC, Per Nordlöw wrote:
> Yes, I thought about that too, but the problem is that std.conv.to is used in std.stdio and I don't want to remember to always to do
>
>     writeln("Some text:", x.to!string);

Or rather

     writeln("Some text:", x.toString);

June 24, 2018
On Sunday, 24 June 2018 at 15:46:58 UTC, Per Nordlöw wrote:
> I would like to see a new trait named, say, `primaryMembers` or `nonAliasMembers` that returns exactly what the switch needs.

Alternatively, we could define a new `__traits(isAlias, enumeratorSymbol)` that evaluates to true for enumerator to aliases and use that to static-if-filter inside the static foreach loop. Comments on that!
June 24, 2018
On Thursday, June 21, 2018 22:46:23 Per Nordlöw via Digitalmars-d wrote:
> I've discovered the annoying fact that std.conv.to doesn't scale
> for enum to string conversion when the enum has hundreds of
> members. This because of a call to `NoDuplicates` which has (at
> least) O(n*log(n) time and space complexity.

I'm certainly not going to argue against trying to make std.conv.to faster, but what on earth are you doing that you have an enum with hundreds of members? I would have thought that an enum that had anywhere near fifty members was enormous, let alone hundreds. I'm not sure that I've ever dealt with an enum that had more than maybe a couple dozen members.

- Jonathan M Davis


June 24, 2018
On Sunday, 24 June 2018 at 22:33:09 UTC, Jonathan M Davis wrote:
> On Thursday, June 21, 2018 22:46:23 Per Nordlöw via Digitalmars-d wrote:
>> I've discovered the annoying fact that std.conv.to doesn't scale
>> for enum to string conversion when the enum has hundreds of
>> members. This because of a call to `NoDuplicates` which has (at
>> least) O(n*log(n) time and space complexity.
>
> I'm certainly not going to argue against trying to make std.conv.to faster, but what on earth are you doing that you have an enum with hundreds of members? I would have thought that an enum that had anywhere near fifty members was enormous, let alone hundreds. I'm not sure that I've ever dealt with an enum that had more than maybe a couple dozen members.
>
> - Jonathan M Davis

A knowledge database. The relation kind enum statically mirrors all the relations in a set of very large ontologies. I might decide to make it dynamically defined instead...
June 24, 2018
On Sunday, 24 June 2018 at 21:47:14 UTC, Per Nordlöw wrote:
> Further, it just struck me that we can generalize my fast solution to include enumerations with enumerator aliases that are defined directly after its original enumerator by checking with  a `static if` if the current enumerator value equals the previous then we skip it. I'm gonna post the solution here after some hacking.

Solution:

@safe:

/** Enumeration wrapper that uses optimized conversion to string (via `toString`
 * member).
 */
struct Enum(E)
if (is(E == enum))
{
    @property string toString() @safe pure nothrow @nogc
    {
        final switch (_enum)
        {
            static foreach (index, member; __traits(allMembers, E))
            {
                static if (index == 0 ||
                           (__traits(getMember, E, __traits(allMembers, E)[index - 1]) !=
                            __traits(getMember, E, member)))
                {
                case __traits(getMember, E, member):
                    return member;
                }
            }
        }
    }
    E _enum;                    // the wrapped enum
    alias _enum this;
}

@safe pure unittest
{
    import std.conv : to;
    enum X { a,
             b,
             _b = b             // enumerator alias
    }
    alias EnumX = Enum!X;
    assert(EnumX(X.a).to!string == "a");
    assert(EnumX(X.b).to!string == "b");
    assert(EnumX(X._b).to!string == "b");
}

June 24, 2018
On 6/24/18 7:13 PM, Per Nordlöw wrote:
> On Sunday, 24 June 2018 at 21:47:14 UTC, Per Nordlöw wrote:
>> Further, it just struck me that we can generalize my fast solution to include enumerations with enumerator aliases that are defined directly after its original enumerator by checking with  a `static if` if the current enumerator value equals the previous then we skip it. I'm gonna post the solution here after some hacking.
> 
> Solution:
> 
> @safe:
> 
> /** Enumeration wrapper that uses optimized conversion to string (via `toString`
>   * member).
>   */
> struct Enum(E)
> if (is(E == enum))
> {
>      @property string toString() @safe pure nothrow @nogc
>      {
>          final switch (_enum)
>          {
>              static foreach (index, member; __traits(allMembers, E))
>              {
>                  static if (index == 0 ||
>                             (__traits(getMember, E, __traits(allMembers, E)[index - 1]) !=
>                              __traits(getMember, E, member)))
>                  {
>                  case __traits(getMember, E, member):
>                      return member;
>                  }
>              }
>          }
>      }
>      E _enum;                    // the wrapped enum
>      alias _enum this;
> }
> 
> @safe pure unittest
> {
>      import std.conv : to;
>      enum X { a,
>               b,
>               _b = b             // enumerator alias
>      }
>      alias EnumX = Enum!X;
>      assert(EnumX(X.a).to!string == "a");
>      assert(EnumX(X.b).to!string == "b");
>      assert(EnumX(X._b).to!string == "b");
> }
> 
Great! That should cover quite a few cases.

Now, about this:

> Yes, I thought about that too, but the problem is that std.conv.to is used in std.stdio and I don't want to remember to always to do
> 
>      writeln("Some text:", x.toString);
> 
> instead of
> 
>      writeln("Some text:", x);

You misunderstand -- I mean fix std.conv.to so it does what you want. I don't want your code to be the only one that benefits, I want all code to benefit ;)

It's shouldn't be that difficult of a PR. Just separate out the part that takes an AliasSeq and makes the switch, and try without and with NoDuplicates (whichever works first wins).

-Steve
June 24, 2018
On Sunday, 24 June 2018 at 23:21:44 UTC, Steven Schveighoffer wrote:
>>      @property string toString() @safe pure nothrow @nogc
>>      {
>>          final switch (_enum)
>>          {
>>              static foreach (index, member; __traits(allMembers, E))
>>              {
>>                  static if (index == 0 ||
>>                             (__traits(getMember, E, __traits(allMembers, E)[index - 1]) !=
>>                              __traits(getMember, E, member)))
>>                  {
>>                  case __traits(getMember, E, member):
>>                      return member;
>>                  }
>>              }
>>          }
>>      }
>>      E _enum;                    // the wrapped enum
>>      alias _enum this;
>> }

Provided that

    __traits(allMembers, E)

is a cheap operation as it's called once for every enumerator. I could get it out of the loop; if I do as

    @property string toString() @safe pure nothrow @nogc
    {
        final switch (_enum)
        {
            enum members = __traits(allMembers, E);
            static foreach (index, member; __traits(allMembers, E))
            {
                static if (index == 0 ||
                           (__traits(getMember, E, members[index - 1]) !=
                            __traits(getMember, E, member)))
                {
                case __traits(getMember, E, member):
                    return member;
                }
            }
        }
    }

the compiler complains as

enum_ex.d(19,29): Error: expression expected as second argument of __traits `getMember`

Is

    __traits(allMembers, ...)

cached by the compiler?
June 24, 2018
On Sunday, 24 June 2018 at 23:34:49 UTC, Per Nordlöw wrote:
>
> Provided that
>
>     __traits(allMembers, E)
>
> is a cheap operation as it's called once for every enumerator. I could get it out of the loop; if I do as
>
>     @property string toString() @safe pure nothrow @nogc
>     {
>         final switch (_enum)
>         {
>             enum members = __traits(allMembers, E);

enum members = [_traits(allMembers, E)];

seems to work