Optionally strongly typed array indexes

bearophile via Digitalmars-d digitalmars-d at puremagic.com
Thu Jun 5 02:22:58 PDT 2014


Sorry for my answers coming so slowly.

Mason McGill:

> Is this considered idiomatic?

Time ago I have asked here to make this index 'i' immutable on 
default, because the literal "0 .. 10" defines an immutable range 
of values:

void main() {
     import std.stdio;
     foreach (i; 0 .. 10) {
         writeln(i);
     }
}


Originally code like this was giving weird results, but thanks to 
Kenji this design bug was fixed, and now this code prints the 
first ten integers normally, so now the situation is not as bad 
as it used to be:

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, but I think it sufficiently safe thanks to the 
explicit 'ref' annotation:

void main() {
     import std.stdio;
     foreach (ref i; 0 .. 10) {
         writeln(i);
         i++;
     }
}


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


void main() {
     import std.stdio;
     foreach (immutable i; 0 .. 10) {
         writeln(i);
         i++; // Not allowed.
     }
}


If your 'i' variable or your function arguments are 
const/immutable, you will have less surprises in your code (and a 
little less bugs). In D it's better to use const/immutable 
everywhere you can. Immutable by default is probably the right 
choice for a modern language. Unfortunately D has missed this 
opportunity.


> 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, you 
don't need to perform a compile-time dimensional analysis for the 
correctness of expressions, you don't need to keep in mind the 
0-based problems of converting Celsius to Fahrenheit, you don't 
need to solve the syntax problems of attaching "m / s" somehow to 
number literals, and so on. So they are two rather different 
problems.


> 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).

Bye,
bearophile


More information about the Digitalmars-d mailing list