Optionally strongly typed array indexes

Mason McGill via Digitalmars-d digitalmars-d at puremagic.com
Thu Jun 5 15:01:55 PDT 2014


On Thursday, 5 June 2014 at 09:23:00 UTC, bearophile wrote:
> Sorry for my answers coming so slowly.

Hah! I'm on these forums (selfishly) trying to understand/improve 
D for my own use at work, while you seem to be 
helping/teaching/contributing for the sake of it. I'm continually 
surprised by how pleasant and professional this community is; 
nothing at all to be sorry for!

> void main() {
>     import std.stdio;
>     foreach (i; 0 .. 10) {
>         writeln(i);
>         i++;
>     }
> }
>
> Currently this prints just the even numbers, I think this is a 
> little weird.

That is weird. It goes counter to the idea of `i` "coming from" 
the range. Though, I'd say someone writing that code is asking 
for strange behavior, so it's not much of an issue.

> Generally in my D code I write like this, as explained here:
> http://blog.knatten.org/2011/11/11/disempower-every-variable/

I agree, with the caveat of trying to not add so much "noise" 
that it becomes unreadable.

> Immutable by default is probably the right choice for a modern 
> language.

Yes, or at least making operations with side effects look 
different from operations without.

>> I think this is one part of the larger problem of representing 
>> units.
>
> Units of measure introduce a good amount of complexity and 
> logic that is absent in the idea of giving strongly types to 
> array indexes.
>
> You don't need to convert inches to centimetres,

True.

> You don't need to perform a compile-time dimensional analysis 
> for the correctness of expressions.

I'd argue you do, at least given my understanding of the goals of 
strongly-typed array indices. For example, consider strided 
indexing:

   const dogs = ["Rover", "Fido", "Rex"].of!"dog";
   const cats = ["Fluffy", "Mr. Whiskers"].of!"cat";

   // This should work.
   const i1 = index!"dog"(1);
   dogs[i1];

   // This should not work.
   const i2 = index!"cat"(1);
   dogs[i2];

   // This should work.
   const i3 = 2 * i1;
   dogs[i3];

   // This should not work.
   const i4 = i2 * i1;
   dogs[i4];

So, the library needs to do some form of dimensional analysis to 
allow typed indices to be multiplied by unit-less scalars, but 
not scalars with units.

> You don't need to keep in mind the 0-based problems of 
> converting Celsius to Fahrenheit.

True. This is why I said the problem of strongly typed array 
indexes is *part* of the problem of units, not equivalent to it.

> You don't need to solve the syntax problems of attaching "m / 
> s" somehow to number literals.

Also true, though as a side note, I think a library solution for 
this could be quite nice:

   enum newton = 1.as!"kg*m/s^2";  // One possibility.
   enum newton = 1*kg*m/square(s); // Another.

>> though I'll have to think about whether it covers all
>> the cases you're interested in.
>
> Another problem is visible in this code, I have a function 
> bar() that must receive a different value for each item of the 
> enum Foo. If I use an associative array, the compiler doesn't 
> catch at compile-time the bug of the missing Foo.B case:
>
>
> enum Foo : ubyte { A, B }
> void bar(int[Foo] aa) {}
> void main() {
>     bar([Foo.A: 10]);
> }
>
>
> And often using an associative array for this purpose is a 
> waste of memory and computation.
>
> The Ada compiler is able to catch this bug at compile-time, 
> using a simple fixed-size array (exactly as long as the number 
> of items in the enum) with indexes strongly typed as Foo. So 
> the array literal must contain all possible indexes only once:
>
>
> import std.traits: EnumMembers;
> enum Foo : ubyte { A, B }
> void bar(int[@typed(Foo) EnumMembers!Foo.length] input) {}
> void main() {
>     bar([Foo.A: 10, Foo.B: 20]);
> }
>
>
> If you give a strong type to the array index you avoid that bug 
> at compile time. (To be accepted as index type, an enumeration 
> must behave nicely, all such tests can be done at compile-time).

That's quite clever, though couldn't it also be done with a 
library?

   enum Foo : ubyte { A, B }
   enum nFoo = EnumMembers!Foo.length;
   alias AllFoo = WithUnits!(int[nFoo], Foo);

   void bar(AllFoo) {}

   void main() {
       bar(AllFoo([/*A*/ 10, /*B*/ 20]));
   }


More information about the Digitalmars-d mailing list