www.digitalmars.com         C & C++   DMDScript  

D - templates issues (long)

reply "Daniel Yokomiso" <daniel_yokomiso yahoo.com.br> writes:
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
Jul 01 2003
parent reply "Walter" <walter digitalmars.com> writes:
"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

 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

 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

 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

 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.
Jul 02 2003
next sibling parent "Daniel Yokomiso" <daniel_yokomiso yahoo.com.br> writes:
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...

 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

 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


 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
Jul 03 2003
prev sibling parent reply "Daniel Yokomiso" <daniel_yokomiso yahoo.com.br> writes:
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...

 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

 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


 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
Jul 03 2003
next sibling parent "Daniel Yokomiso" <daniel_yokomiso yahoo.com.br> writes:
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
Jul 03 2003
prev sibling parent "Walter" <walter digitalmars.com> writes:
"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...

 That is an intriguing idea. But it may only work if derived templates


 always "more specialized" than the base templates. For example, the


 one above wouldn't work because complex is not more specialized than

 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

 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,

 others we defined traits classes implementing the interface and then in

 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



 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


 D
 we don't have dependent template types.

I see what you're trying to do here, but I'm not quite sure what


 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


 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,

 it's generic until we apply it, because it works for many different types

 '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

 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

 be harmless. After all we're talking about semantics of templates :)
     As D won't allow things different from types and integers, then

 we're in safe grounds here. We just need to forbid everything that can't

 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

 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

 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,

 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!
Jul 07 2003