December 17, 2012
Regarding the recent discussions on SafeInts and HalfFloats, I find some difficulties in using user-defined sub-range of a type. Let's say I want to define a new type as subset of all the chars, that contains only the digits.

This is a very common need in my programs, I often want integral values that are allowed to be only a subrange (or subset, if they are sparse) of a built-in integral type.

(In Ada language there is syntax and semantics in the type system to make this easy, safe and nice).

Here I have shown some general problems associated with the creation and use of user-defined subtypes:

http://forum.dlang.org/thread/jhkbsghxjmdrxoxaevzm@forum.dlang.org#post-jhkbsghxjmdrxoxaevzm:40forum.dlang.org


This program shows two problems that make subtyping much less handy/useful for me in D:


// Start program -------------------------------
struct Digit {
    immutable char d;

    this(in char d_) pure nothrow
    in { assert(d_ >= '0' && d_ <= '9'); }
    body { this.d = d_; }

    alias d this;
}

U[] _validator(U, T)(in T items) pure nothrow {
    typeof(return) result;
    foreach (immutable d; items)
        result ~= U(d);
    return result;
}

template Digits(string s) {
    enum Digits = _validator!Digit(s);
}

void main() {
    import std.string: countchars;
    immutable d1 = "12341234";

    immutable n1 = countchars(d1, "23");
    assert(n1 == 4);

    // No array contravariance (through the Digit pre-condition):
    Digit[] d2 = d1; // error.

    Digit[] d3 = Digits!d1; // OK

    // No array covariance, countchars requires a
    // immutable(char)[] and refused a Digits[]:
    immutable n2 = countchars(d3, "23"); // error
    immutable n3 = countchars(d3, Digits!"23"); // error
}
// End program -------------------------------


The first problem is with array literals (here the array literals are strings, but a normal dynamic array of chars is a similar use case). If I define a subtype, like Digits, then I'd like a nice ways to write them in an array/associative array literal. Such literals should be compact, readable and yet they should be safe, this means if in a string meant to become a Digits[] I put a "x", the compiler should give me a compile-time error instead of a run-time error.

To solve that contravariance+validation problem here I've used a template and a compile-time function helper. It's not a too much bad solution, but maybe there are ways to improve the situation.

The other problem is that a Digit[] despite being meant as a subtype of char[], doesn't have covariance, so std.string.countchars() doesn't accept a Digit[]. This makes such subtyping much less useful.

A built-in annotation like this doesn't solve the problem:

@subtype alias d this;


Maybe a built-in annotation this this is enough, I don't know:

@subtype(char) struct Digit { ... }

Bye,
bearophile