Thread overview
Efficient enum array keys?
Apr 11, 2019
Julian
Apr 11, 2019
Basile B.
Apr 11, 2019
Julian
Apr 11, 2019
Basile B.
April 11, 2019
Hello,

When reading through the following D blog post, I noticed in the
feature chart that D had "Arrays beginning at arbitrary indices" as
a +1 feature, the same as in Ada.

https://dlang.org/blog/2018/06/20/how-an-engineering-company-chose-to-migrate-to-d/

That surprised me, and from the code with the blog, that seems to be
generous. In Ada you can just say

  Silly : array (2 .. 7) of Integer;

to stack-allocate a six-integer array, with the first accessible
integer at Silly(2) and the last integer at Silly(7). Meanwhile the
blog has some epcomat thing that provides this to D:

  alias t = StaticArray!(int, 2, 20);

  t a;

  for (n = 2; n <= 20; n++)
      a[n] = n;

That 'Silly' array isn't very serious. I got a lot more use out of
Ada's feature of any discrete type being usable as an array index.
This gives you the efficiency of normal arrays but with a huge
readability boost. For an example from last year's Advent of Code:

  N : constant Natural := 32;
  type Rune is ('#', 'G', 'E', '.');
  type Runestring is array (1 .. N) of Rune;
  Maze : array (1 .. N) of Runestring :=
    ("################################",
     "#######..#######..#.G..##..#####",
     "######.....#####.....GG.##.#####",
     ... more of this ...);

  subtype Living_Runes is Rune range 'G' .. 'E';
  Initial_States_Table : constant array (Living_Runes) of Unit_State :=
    ('G' => (Hit_Points => 200, Attack_Power => 3, Moved => False),
     'E' => (Hit_Points => 200, Attack_Power => 3, Moved => False));

So you can refer to Initial_States_Table('G').Hit_Points to know
how healthy the goblins start out in this game.

Thinking of that, I came up with the following D code:

  import std.stdio, core.exception;

  enum Days { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday }

  void main() {
      int[Days] worklog;
      ++worklog[Days.Wednesday];
      writeln("- worklog");
      dumplog(worklog);

      writeln();

      int[Days.max+1] altlog;
      ++altlog[Days.Saturday];
      writeln("- altlog");
      dumplog(altlog);
  }

  void dumplog(T)(T log) {
      foreach (day; Days.Sunday .. Days.Saturday) {
          try {
              writefln("%5d %s", log[day], day);
          }
          catch (core.exception.RangeError e) {}
      }
  }

Which has this output:

  - worklog
      1 Wednesday

  - altlog
      0 Sunday
      0 Monday
      0 Tuesday
      0 Wednesday
      0 Thursday
      0 Friday

And which has these faults, vs. the Ada equivalent:

1. worklog is an associative array and ++worklog[Days.Wednesday]
   compiles to a function call. This is more flexible of course but
   here it's unwanted and more expensive than a simple array.

2. worklog needs explicit initialization

3. this is ugly: int[Days.max+1] altlog

4. I can't write foreach (day; Days) { } ?

5. the foreach in that code is wrong: it skips Saturday, and the
   obvious fix of +1 is both ugly and an error:

  Error: cannot implicitly convert expression day of type int to Days

This works:

  foreach (day; Days.min .. Days.max+1)
      writefln("%5d %s", log[cast(Days) day], cast(Days) day);

But compare to Ada:

  with Ada.Text_IO; use Ada.Text_IO;

  procedure Enum is
     type Weekdays is
       (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday);

     procedure What_Day (Day : Weekdays) is
     begin
        case Day is
           when Monday | Tuesday | Wednesday | Thursday | Friday =>
              Put_Line (Weekdays'Image (Day) & " is a weekday");
           when Saturday | Sunday =>
              Put_Line (Weekdays'Image (Day) & " is a weekend day");
        end case;
     end What_Day;
  begin
     for J in Weekdays'Range loop
        What_Day (J);
     end loop;
  end Enum;

... which, OK, has its own problems:

  MONDAY is a weekday
  TUESDAY is a weekday
  WEDNESDAY is a weekday
  THURSDAY is a weekday
  FRIDAY is a weekday
  SATURDAY is a weekend day
  SUNDAY is a weekend day

Is there a nicer way to have enum array keys in D?
April 11, 2019
On Thursday, 11 April 2019 at 06:20:05 UTC, Julian wrote:
> Hello,
>
> When reading through the following D blog post, I noticed in the
> feature chart that D had "Arrays beginning at arbitrary indices" as
> a +1 feature, the same as in Ada.
>
> https://dlang.org/blog/2018/06/20/how-an-engineering-company-chose-to-migrate-to-d/
>
> That surprised me, and from the code with the blog, that seems to be
> generous.
>
> [...]
>
> Is there a nicer way to have enum array keys in D?

No. I've myself written my own EnumIndexedArray [1] type. It's pretty simple. Just a couple of operator overload to preovide the syntax.

I went from ObjFPC/Delphi which has what you describe from Ada too and missed it.
(typically: `enum TStuff = (); var stuffStrings: array[TStuff] of string;` ...)

[1] https://github.com/Basile-z/iz/blob/9ce6fc0e2e0c74f97d530ce598a6842b7b048f25/import/iz/enumset.d#L1086
April 11, 2019
On Thursday, 11 April 2019 at 06:45:23 UTC, Basile B. wrote:
> On Thursday, 11 April 2019 at 06:20:05 UTC, Julian wrote:
>> Is there a nicer way to have enum array keys in D?
>
> No. I've myself written my own EnumIndexedArray [1] type. It's pretty simple. Just a couple of operator overload to preovide the syntax.
>
> I went from ObjFPC/Delphi which has what you describe from Ada too and missed it.
> (typically: `enum TStuff = (); var stuffStrings: array[TStuff] of string;` ...)
>
> [1] https://github.com/Basile-z/iz/blob/9ce6fc0e2e0c74f97d530ce598a6842b7b048f25/import/iz/enumset.d#L1086

Thanks. That still seems like enough work that I'd rather
do things the D way. At least if I don't also want Enum sets.

That gave me the idea for this though:

  import std.stdio;

  struct EnumRange(E) {
      int begin = E.min;
      int end = E.max + 1;
      bool empty() { return begin == end; }
      void popFront() { ++begin; }
      E front() { return cast(E) begin; }
  }

  enum Days { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday }

  void main() {
      int[Days.max+1] worklog;
      ++worklog[Days.Saturday];
      writeln("- worklog");
      EnumRange!(Days) why;
      foreach (day; why)
          writefln("%5d %s", worklog[day], day);
  }

Which I'm still disappointed is not:

   foreach (day; EnumRange!(Days))

Also, this isn't too bad:

  void main() {
      int[Days.max+1] worklog;
      ++worklog[Days.Saturday];
      writeln("- worklog");
      foreach (day, count; worklog)
          writefln("%5d %s", count, cast(Days) day);
  }

I don't see a difference in micro-benchmarks. *shrug*
April 11, 2019
On Thursday, 11 April 2019 at 07:56:42 UTC, Julian wrote:
> On Thursday, 11 April 2019 at 06:45:23 UTC, Basile B. wrote:
>> On Thursday, 11 April 2019 at 06:20:05 UTC, Julian wrote:
> I don't see a difference in micro-benchmarks. *shrug*

Your enum is int so in machine code it's exactly like processing a machine word.
It's not even worth benchmarking this ;)

As a side note, and in case you would not know it yet, there's this traits that's useful with enums: https://dlang.org/phobos/std_traits.html#EnumMembers