Thread overview
templates issues (long)
Jul 02, 2003
Daniel Yokomiso
Jul 02, 2003
Walter
Jul 03, 2003
Daniel Yokomiso
Jul 03, 2003
Daniel Yokomiso
Jul 03, 2003
Daniel Yokomiso
Jul 07, 2003
Walter
July 02, 2003
Hi,

    There's a couple of issues with templates in D today. I knew they
existed but today I stumbled in situations where the current template design
won't be enough. First there is a problem regarding multiple template
declarations. Each declaration is a distinct, unrelated, scope so if we
define a specialized template version we can't see what's declared in the
most generic one.


template TAssert(T) {
    void isEqual(T expected, T received) {
        if (expected != receibed) {
            printf("Ops!\r\n");
            assert(false);
        }
    }
}
template TAssert(T : real) {
    void isEqual(T expected, T received, real precision) {
        T delta = received * precision;
        if ((expected < (received - delta)) || (expected > (received +
delta))) {
            printf("Ops!\r\n");
            assert(false);
        }
    }
}

int main() {
    instance TAssert(real) test;
    test.isEqual(1.0, 1.0);
    test.isEqual(1.0, 1.1, 0.1);
    return 0;
}


    In main the third line is ok, but the second is problematic. We have
some workarounds here: copy the features from the most generic to the most
specific (this kind of code duplication wasn't a problem in D to be solved
by templates?), or force the client to instantiate two templates and don't
use multiple declarations. In minor situations this may not be too
cumbersome, but in larger codebase it'll become an issue. Of cource C++
programmers say: "Due to the wonders of implicit instantion we don't have
this problem". BTW in this case explicit instantiation gave us a nice way to
name things, with the "test.isEqual" naming. Template extension could help
us here, like:


template TAssert(T)
template TAssert(T : real) : TAssert(T)
template TAssert(T : complex) : TAssert(T : real)


or some other kind of syntax. With this semantics we can define several declarations and extend them one piece at a time, ensuring good modularization. This could work to: define classes/functions in one declaration and reuse them in the specialized declaration, define a class and in specialized declarations add new methods/fields to it. Without it we kludge either the library or the client.

    The second issue is of templated methods. Lets look at an example of
type safe units in D (using integer template parameters):

template TUnit(T, int C, int G, int S) {
    struct Quantity {
        T value;
        Quantity add(Quantity other) {
            Quantity result;
            result.value = value + other.value;
            return result;
        }
        Quantity mul(Quantity other) {
            Quantity result;
            result.value = value * other.value;
            return result;
        }
    }
}

template TUnits(T) {
    alias instance TUnit(T, 1, 0, 0).Quantity Length;
    alias instance TUnit(T, 0, 1, 0).Quantity Mass;
    alias instance TUnit(T, 0, 0, 1).Quantity Time;
    alias instance TUnit(T, 2, 0, 0).Quantity Area;
}

int main() {
    instance TUnits(real) units;
    units.Length a, b, c;
    units.Area d;
    a.value = 100;
    b = a + a;     // me and compiler think it's ok;
    c = a * b;     // this doesn't look right to me, but the compiler
disagrees
    d = a * b;     // this should be ok, but the compiler disagrees again.
                   //   dumb compiler, don't you know physics!?!?
}


    I guess the compiler doesn't know physics, but my declaration was
incorrect. What I wanted to do was:


template TUnit(T, int C, int G, int S) {
    struct Quantity {
        T value;
        Quantity add(Quantity other) {
            Quantity result;
            result.value = value + other.value;
            return result;
        }
        instance TUnit(T, C + C1, G + G1, S + S1).Quantity mul(instance
TUnit(T, C1, G1, S1).Quantity other) {
            Quantity result;
            result.value = value * other.value;
            return result;
        }
    }
}


    Which is correct for me and this imaginary compiler. But currently in D
we don't have dependent template types. Note that it's different from adding
arbitrary methods based on internal templates (explicit documented in the
spec). It require's a little bit of template meta-programming support on the
language, so the compiler can recognize different levels of typing. We could
do it in a different way:


template TPair(T, U) {
    struct Pair {
        T left;
        U right;
    }
}

template TValue(T) {
    struct Value {
        T value;
    }
    instance TPair(T, U).Pair makePair(Value left, instance TValue(U).Value
right) {
        instance TPair(T, U).Pair result;
        result.left = left.value;
        result.right = right.value;
        return result;
    }
}

    Of course this example is silly, but the other wasn't. If this kind of
code was possible we could do some template meta-programming in D, using the
same tricks. But I think that explicit instantiation will help to make this
kind of "trick" simpler to understand, for the compiler and the programmer.
Also this feature is essential to do correct abstractions and it improves
the expressiveness of the type system (not all type systems are equivalent,
different from most programming languages being turing complete). I know I
want this feature, some kind of libraries in deimos (deimos.science,
deimos.vector and deimos.math) would use this stuff heavily and help the
programmers to avoid recreating the wheel.
    This issue arises from time to time, and each time I'm more convinced
about the importance of such features. It helps library writers, don't
burden application programmers (these roles may overlap, but it's not the
point here) and improve reusability and correctness of D programs. Of course
today D's libraries can be more complete than Java, because Java so far
(i.e. 1.5 isn't there yet) has no support for generics. But in the next year
Java'll have generics WITH dependent types (but no integer specialization).
How can we decide to convince C++ programmers to use D if we'll have to say
"But in some cases you'll have to duplicate your code and forget the
type-safety C++ was giving you. Sorry.".

    Best regards,
    Daniel Yokomiso.


"Actually, C++ is being post-incremented, so this iteration C++ is the same
as C, but next time around it'll be great!"
 - tfinniga at /.



---
Outgoing mail is certified Virus Free.
Checked by AVG anti-virus system (http://www.grisoft.com).
Version: 6.0.491 / Virus Database: 290 - Release Date: 18/6/2003


July 02, 2003
"Daniel Yokomiso" <daniel_yokomiso@yahoo.com.br> wrote in message news:bdt914$14s4$1@digitaldaemon.com...
> Hi,
>
>     There's a couple of issues with templates in D today. I knew they
> existed but today I stumbled in situations where the current template
design
> won't be enough. First there is a problem regarding multiple template declarations. Each declaration is a distinct, unrelated, scope so if we define a specialized template version we can't see what's declared in the most generic one.
>
>
> template TAssert(T) {
>     void isEqual(T expected, T received) {
>         if (expected != receibed) {
>             printf("Ops!\r\n");
>             assert(false);
>         }
>     }
> }
> template TAssert(T : real) {
>     void isEqual(T expected, T received, real precision) {
>         T delta = received * precision;
>         if ((expected < (received - delta)) || (expected > (received +
> delta))) {
>             printf("Ops!\r\n");
>             assert(false);
>         }
>     }
> }
>
> int main() {
>     instance TAssert(real) test;
>     test.isEqual(1.0, 1.0);
>     test.isEqual(1.0, 1.1, 0.1);
>     return 0;
> }
>
>
>     In main the third line is ok, but the second is problematic. We have
> some workarounds here: copy the features from the most generic to the most
> specific (this kind of code duplication wasn't a problem in D to be solved
> by templates?), or force the client to instantiate two templates and don't
> use multiple declarations. In minor situations this may not be too
> cumbersome, but in larger codebase it'll become an issue. Of cource C++
> programmers say: "Due to the wonders of implicit instantion we don't have
> this problem". BTW in this case explicit instantiation gave us a nice way
to
> name things, with the "test.isEqual" naming. Template extension could help us here, like:
>
>
> template TAssert(T)
> template TAssert(T : real) : TAssert(T)
> template TAssert(T : complex) : TAssert(T : real)
>
>
> or some other kind of syntax. With this semantics we can define several declarations and extend them one piece at a time, ensuring good modularization. This could work to: define classes/functions in one declaration and reuse them in the specialized declaration, define a class and in specialized declarations add new methods/fields to it. Without it
we
> kludge either the library or the client.

That is an intriguing idea. But it may only work if derived templates are always "more specialized" than the base templates. For example, the third one above wouldn't work because complex is not more specialized than real - and so what is the type of T in the specialization? real or complex?


>     The second issue is of templated methods. Lets look at an example of
> type safe units in D (using integer template parameters):
>
> template TUnit(T, int C, int G, int S) {
>     struct Quantity {
>         T value;
>         Quantity add(Quantity other) {
>             Quantity result;
>             result.value = value + other.value;
>             return result;
>         }
>         Quantity mul(Quantity other) {
>             Quantity result;
>             result.value = value * other.value;
>             return result;
>         }
>     }
> }
>
> template TUnits(T) {
>     alias instance TUnit(T, 1, 0, 0).Quantity Length;
>     alias instance TUnit(T, 0, 1, 0).Quantity Mass;
>     alias instance TUnit(T, 0, 0, 1).Quantity Time;
>     alias instance TUnit(T, 2, 0, 0).Quantity Area;
> }
>
> int main() {
>     instance TUnits(real) units;
>     units.Length a, b, c;
>     units.Area d;
>     a.value = 100;
>     b = a + a;     // me and compiler think it's ok;
>     c = a * b;     // this doesn't look right to me, but the compiler
> disagrees
>     d = a * b;     // this should be ok, but the compiler disagrees again.
>                    //   dumb compiler, don't you know physics!?!?
> }
>
>
>     I guess the compiler doesn't know physics, but my declaration was
> incorrect. What I wanted to do was:
>
>
> template TUnit(T, int C, int G, int S) {
>     struct Quantity {
>         T value;
>         Quantity add(Quantity other) {
>             Quantity result;
>             result.value = value + other.value;
>             return result;
>         }
>         instance TUnit(T, C + C1, G + G1, S + S1).Quantity mul(instance
> TUnit(T, C1, G1, S1).Quantity other) {
>             Quantity result;
>             result.value = value * other.value;
>             return result;
>         }
>     }
> }
>
>
>     Which is correct for me and this imaginary compiler. But currently in
D
> we don't have dependent template types.

I see what you're trying to do here, but I'm not quite sure what dependent template types are <g>. The above should work if C1, G1, and S1 are constants, but it's possible that then the instatiation of TUnit would be infinite recursion.



July 03, 2003
Hi,

    Comments embedded.

----- Original Message -----
From: "Walter" <walter@digitalmars.com>
Newsgroups: D
Sent: Wednesday, July 02, 2003 5:19 AM
Subject: Re: templates issues (long)


>
> "Daniel Yokomiso" <daniel_yokomiso@yahoo.com.br> wrote in message news:bdt914$14s4$1@digitaldaemon.com...
[snip]
>
> That is an intriguing idea. But it may only work if derived templates are always "more specialized" than the base templates. For example, the third one above wouldn't work because complex is not more specialized than
real -
> and so what is the type of T in the specialization? real or complex?

    The last one was wrong (sigh, this always happen when I post uncompiled
code). I meant float instead of complex. Also if we can define template
extensions, we could enhance it with abstract templates (or interface
templates, if you prefer):


abstract template TAbstractStringable(T) {
    abstract char[] toString(T value);
}

template TStringable(T : char[]) : TStringable(T) {
    char[] toString(T value) {
        return value;
    }
}

template TStringable(T : int) : TStringable(T) {
    char[] toString(T value) {
        return dig.fmt("%d", value);
    }
}

template TSomething(T) {
    private instance TStringable(T) stringfier;
    public void print(T value) {
        printf("%.*s\r\n", stringfier.toString(value));
    }
}


    Today we can workaround this using a template to define a interface, in
others we defined traits classes implementing the interface and then in the
client template we keep a private variable to store the traits' instances.
All this to use a classless function, and I thought D let me define
functions outside classes if I only needed the function ;)

[snip]
> > template TUnit(T, int C, int G, int S) {
> >     struct Quantity {
> >         T value;
> >         Quantity add(Quantity other) {
> >             Quantity result;
> >             result.value = value + other.value;
> >             return result;
> >         }
> >         instance TUnit(T, C + C1, G + G1, S + S1).Quantity mul(instance
> > TUnit(T, C1, G1, S1).Quantity other) {
> >             Quantity result;
> >             result.value = value * other.value;
> >             return result;
> >         }
> >     }
> > }
> >
> >
> >     Which is correct for me and this imaginary compiler. But currently
in
> D
> > we don't have dependent template types.
>
> I see what you're trying to do here, but I'm not quite sure what dependent template types are <g>. The above should work if C1, G1, and S1 are constants, but it's possible that then the instatiation of TUnit would be infinite recursion.

    There I was telling the compiler: "Look, this operation 'mul' gives a
result such as its type depends on their parameters types: the implicit
'this' type (instance TUnit(T,C,G,S)) and the 'other' type (instance
TUnit(T,C1,G1,S1)). So, compiler, this declaration, should be type-safe, but
it's generic until we apply it, because it works for many different types of
'other'." In this case there's no "infinite recursion", we aren't
instantiating the parameter type, just defining a type rule. perhaps this
syntax is better:

    template TUnit(T, C + C1, G + G1, S + S1).Quantity mul(template TUnit(T,
C1, G1, S1).Quantity other)

or

    TUnit(T, C + C1, G + G1, S + S1).Quantity mul(TUnit(T, C1, G1,
S1).Quantity other)

or, with a new keyword

    instance TUnit(T, C + C1, G + G1, S + S1).Quantity mul(forall TUnit(T,
C1, G1, S1).Quantity other)

or even (C++ like):

    template TMul(C1, G1, S1) {
        instance TUnit(T, C + C1, G + G1, S + S1).Quantity mul(instance
TUnit(T, C1, G1, S1).Quantity other)
    }

    I thought that overloading the instance keyword in this situation would
be harmless. After all we're talking about semantics of templates :)
    As D won't allow things different from types and integers, then (AFAICT)
we're in safe grounds here. We just need to forbid everything that can't be
resolved by the compiler, so in the dependent type (i.e. the result type)
there can only be references to the types in scope (the parameter is in
scope here) and the integer template parameters. Also only primitive
operations (i.e. no user-defined functions) should be allowed. That, while
limiting the expressiveness, gives a clear and simple definition that covers
99% of the usage (mainly mine ;). This code should work ok:


// should work even without this proposal
alias instance TUnit(real,1,0,0).Quantity Length;
alias instance TUnit(real,2,0,0).Quantity Area;
alias instance TUnit(real,3,0,0).Quantity Volume;

Length a = Length.create(3);
Length b = Length.create(4);

// the first '*' has a type different from the second '*'
Area c = a * b;   // * : TUnit(real,1,0,0) -> TUnit(real,1,0,0) ->
TUnit(real,2,0,0)
Volume d = a * c; // * : TUnit(real,1,0,0) -> TUnit(real,2,0,0) ->
TUnit(real,3,0,0)


    But we shouldn't allow expressions in the parameter types, because this
is almost insane (i.e. the type system should be very, very, different).
    IMHO this is a big issue, anywhere I look (e.g. type-safe units, linear
algebra, matrices) I see math on types (e.g. multi-dimensional matrix
slicing, vector fields, integral calculus). If D suports this stuff, terse
and efficiently, we could convince more people to use it. With the correct
optimizations D could even beat Fortran speed with smaller and safer code.

    Best regards,
    Daniel Yokomiso.

"The only difference between me and a madman is that I am not mad."
 - Salvador Dali



---
Outgoing mail is certified Virus Free.
Checked by AVG anti-virus system (http://www.grisoft.com).
Version: 6.0.491 / Virus Database: 290 - Release Date: 18/6/2003


July 03, 2003
Hi,

    Comments embedded.

----- Original Message -----
From: "Walter" <walter@digitalmars.com>
Newsgroups: D
Sent: Wednesday, July 02, 2003 5:19 AM
Subject: Re: templates issues (long)


>
> "Daniel Yokomiso" <daniel_yokomiso@yahoo.com.br> wrote in message news:bdt914$14s4$1@digitaldaemon.com...
[snip]
>
> That is an intriguing idea. But it may only work if derived templates are always "more specialized" than the base templates. For example, the third one above wouldn't work because complex is not more specialized than
real -
> and so what is the type of T in the specialization? real or complex?

    The last one was wrong (sigh, this always happen when I post uncompiled
code). I meant float instead of complex. Also if we can define template
extensions, we could enhance it with abstract templates (or interface
templates, if you prefer):


abstract template TAbstractStringable(T) {
    abstract char[] toString(T value);
}

template TStringable(T : char[]) : TStringable(T) {
    char[] toString(T value) {
        return value;
    }
}

template TStringable(T : int) : TStringable(T) {
    char[] toString(T value) {
        return dig.fmt("%d", value);
    }
}

template TSomething(T) {
    private instance TStringable(T) stringfier;
    public void print(T value) {
        printf("%.*s\r\n", stringfier.toString(value));
    }
}


    Today we can workaround this using a template to define a interface, in
others we defined traits classes implementing the interface and then in the
client template we keep a private variable to store the traits' instances.
All this to use a classless function, and I thought D let me define
functions outside classes if I only needed the function ;)

[snip]
> > template TUnit(T, int C, int G, int S) {
> >     struct Quantity {
> >         T value;
> >         Quantity add(Quantity other) {
> >             Quantity result;
> >             result.value = value + other.value;
> >             return result;
> >         }
> >         instance TUnit(T, C + C1, G + G1, S + S1).Quantity mul(instance
> > TUnit(T, C1, G1, S1).Quantity other) {
> >             Quantity result;
> >             result.value = value * other.value;
> >             return result;
> >         }
> >     }
> > }
> >
> >
> >     Which is correct for me and this imaginary compiler. But currently
in
> D
> > we don't have dependent template types.
>
> I see what you're trying to do here, but I'm not quite sure what dependent template types are <g>. The above should work if C1, G1, and S1 are constants, but it's possible that then the instatiation of TUnit would be infinite recursion.

    There I was telling the compiler: "Look, this operation 'mul' gives a
result such as its type depends on their parameters types: the implicit
'this' type (instance TUnit(T,C,G,S)) and the 'other' type (instance
TUnit(T,C1,G1,S1)). So, compiler, this declaration, should be type-safe, but
it's generic until we apply it, because it works for many different types of
'other'." In this case there's no "infinite recursion", we aren't
instantiating the parameter type, just defining a type rule. perhaps this
syntax is better:

    template TUnit(T, C + C1, G + G1, S + S1).Quantity mul(template TUnit(T,
C1, G1, S1).Quantity other)

or

    TUnit(T, C + C1, G + G1, S + S1).Quantity mul(TUnit(T, C1, G1,
S1).Quantity other)

or, with a new keyword

    instance TUnit(T, C + C1, G + G1, S + S1).Quantity mul(forall TUnit(T,
C1, G1, S1).Quantity other)

or even (C++ like, but that would require "evil" implicit instantiation):

    template TMul(C1, G1, S1) {
        instance TUnit(T, C + C1, G + G1, S + S1).Quantity mul(instance
TUnit(T, C1, G1, S1).Quantity other)
    }

    I thought that overloading the instance keyword in this situation would
be harmless. After all we're talking about semantics of templates :)
    As D won't allow things different from types and integers, then (AFAICT)
we're in safe grounds here. We just need to forbid everything that can't be
resolved by the compiler, so in the dependent type (i.e. the result type)
there can only be references to the types in scope (the parameter is in
scope here) and the integer template parameters. Also only primitive
operations (i.e. no user-defined functions) should be allowed. That, while
limiting the expressiveness, gives a clear and simple definition that covers
99% of the usage (mainly mine ;). This code should work ok:


// should work even without this proposal
alias instance TUnit(real,1,0,0).Quantity Length;
alias instance TUnit(real,2,0,0).Quantity Area;
alias instance TUnit(real,3,0,0).Quantity Volume;

Length a = Length.create(3);
Length b = Length.create(4);

// the first '*' has a type different from the second '*'
Area c = a * b;   // * : TUnit(real,1,0,0) -> TUnit(real,1,0,0) ->
TUnit(real,2,0,0)
Volume d = a * c; // * : TUnit(real,1,0,0) -> TUnit(real,2,0,0) ->
TUnit(real,3,0,0)


    But we shouldn't allow expressions in the parameter types, because this
is almost insane (i.e. the type system should be very, very, different).
    IMHO this is a big issue, anywhere I look (e.g. type-safe units, linear
algebra, matrices) I see math on types (e.g. multi-dimensional matrix
slicing, vector fields, integral calculus). If D suports this stuff, terse
and efficiently, we could convince more people to use it. With the correct
optimizations D could even beat Fortran speed with smaller and safer code.

    Best regards,
    Daniel Yokomiso.

"The only difference between me and a madman is that I am not mad."
 - Salvador Dali



---
Outgoing mail is certified Virus Free.
Checked by AVG anti-virus system (http://www.grisoft.com).
Version: 6.0.491 / Virus Database: 290 - Release Date: 18/6/2003



July 03, 2003
Sorry for the duplicated post. I cancelled (in my client) before sending,
but it didn't work.


---
Outgoing mail is certified Virus Free.
Checked by AVG anti-virus system (http://www.grisoft.com).
Version: 6.0.491 / Virus Database: 290 - Release Date: 18/6/2003


July 07, 2003
"Daniel Yokomiso" <daniel_yokomiso@yahoo.com.br> wrote in message news:be0v2n$22u1$1@digitaldaemon.com...
> ----- Original Message -----
> From: "Walter" <walter@digitalmars.com>
> > "Daniel Yokomiso" <daniel_yokomiso@yahoo.com.br> wrote in message news:bdt914$14s4$1@digitaldaemon.com...
> [snip]
> >
> > That is an intriguing idea. But it may only work if derived templates
are
> > always "more specialized" than the base templates. For example, the
third
> > one above wouldn't work because complex is not more specialized than
> real -
> > and so what is the type of T in the specialization? real or complex?
>
>     The last one was wrong (sigh, this always happen when I post
uncompiled
> code). I meant float instead of complex. Also if we can define template extensions, we could enhance it with abstract templates (or interface templates, if you prefer):

I still don't think it works with float, with my same comment.


> abstract template TAbstractStringable(T) {
>     abstract char[] toString(T value);
> }

I'm sure you meant TStringable, not TAbstractStringable??


> template TStringable(T : char[]) : TStringable(T) {
>     char[] toString(T value) {
>         return value;
>     }
> }
>
> template TStringable(T : int) : TStringable(T) {
>     char[] toString(T value) {
>         return dig.fmt("%d", value);
>     }
> }
>
> template TSomething(T) {
>     private instance TStringable(T) stringfier;
>     public void print(T value) {
>         printf("%.*s\r\n", stringfier.toString(value));
>     }
> }
>
>
>     Today we can workaround this using a template to define a interface,
in
> others we defined traits classes implementing the interface and then in
the
> client template we keep a private variable to store the traits' instances. All this to use a classless function, and I thought D let me define functions outside classes if I only needed the function ;)

In the example you gave, I just don't see why it needs a 'base' template. It will do what you want without it, at least for the example.


> [snip]
> > > template TUnit(T, int C, int G, int S) {
> > >     struct Quantity {
> > >         T value;
> > >         Quantity add(Quantity other) {
> > >             Quantity result;
> > >             result.value = value + other.value;
> > >             return result;
> > >         }
> > >         instance TUnit(T, C + C1, G + G1, S + S1).Quantity
mul(instance
> > > TUnit(T, C1, G1, S1).Quantity other) {
> > >             Quantity result;
> > >             result.value = value * other.value;
> > >             return result;
> > >         }
> > >     }
> > > }
> > >
> > >
> > >     Which is correct for me and this imaginary compiler. But currently
> in
> > D
> > > we don't have dependent template types.
> >
> > I see what you're trying to do here, but I'm not quite sure what
dependent
> > template types are <g>. The above should work if C1, G1, and S1 are constants, but it's possible that then the instatiation of TUnit would
be
> > infinite recursion.
>
>     There I was telling the compiler: "Look, this operation 'mul' gives a
> result such as its type depends on their parameters types: the implicit
> 'this' type (instance TUnit(T,C,G,S)) and the 'other' type (instance
> TUnit(T,C1,G1,S1)). So, compiler, this declaration, should be type-safe,
but
> it's generic until we apply it, because it works for many different types
of
> 'other'." In this case there's no "infinite recursion", we aren't instantiating the parameter type, just defining a type rule. perhaps this syntax is better:

Unfortunately, the way the compiler works it is instantiating the template recursively. That's the only way it can determine what '.Quantity' is.


>     template TUnit(T, C + C1, G + G1, S + S1).Quantity mul(template
TUnit(T,
> C1, G1, S1).Quantity other)
>
> or
>
>     TUnit(T, C + C1, G + G1, S + S1).Quantity mul(TUnit(T, C1, G1,
> S1).Quantity other)
>
> or, with a new keyword
>
>     instance TUnit(T, C + C1, G + G1, S + S1).Quantity mul(forall TUnit(T,
> C1, G1, S1).Quantity other)
>
> or even (C++ like, but that would require "evil" implicit instantiation):
>
>     template TMul(C1, G1, S1) {
>         instance TUnit(T, C + C1, G + G1, S + S1).Quantity mul(instance
> TUnit(T, C1, G1, S1).Quantity other)
>     }
>
>     I thought that overloading the instance keyword in this situation
would
> be harmless. After all we're talking about semantics of templates :)
>     As D won't allow things different from types and integers, then
(AFAICT)
> we're in safe grounds here. We just need to forbid everything that can't
be
> resolved by the compiler, so in the dependent type (i.e. the result type) there can only be references to the types in scope (the parameter is in scope here) and the integer template parameters. Also only primitive operations (i.e. no user-defined functions) should be allowed. That, while limiting the expressiveness, gives a clear and simple definition that
covers
> 99% of the usage (mainly mine ;). This code should work ok:
>
>
> // should work even without this proposal
> alias instance TUnit(real,1,0,0).Quantity Length;
> alias instance TUnit(real,2,0,0).Quantity Area;
> alias instance TUnit(real,3,0,0).Quantity Volume;
>
> Length a = Length.create(3);
> Length b = Length.create(4);
>
> // the first '*' has a type different from the second '*'
> Area c = a * b;   // * : TUnit(real,1,0,0) -> TUnit(real,1,0,0) ->
> TUnit(real,2,0,0)
> Volume d = a * c; // * : TUnit(real,1,0,0) -> TUnit(real,2,0,0) ->
> TUnit(real,3,0,0)
>
>
>     But we shouldn't allow expressions in the parameter types, because
this
> is almost insane (i.e. the type system should be very, very, different).
>     IMHO this is a big issue, anywhere I look (e.g. type-safe units,
linear
> algebra, matrices) I see math on types (e.g. multi-dimensional matrix slicing, vector fields, integral calculus). If D suports this stuff, terse and efficiently, we could convince more people to use it. With the correct optimizations D could even beat Fortran speed with smaller and safer code.

I need to learn more about this!