D - templates issues (long)
- Daniel Yokomiso (155/155) Jul 01 2003 Hi,
- Walter (14/118) Jul 02 2003 design
- Daniel Yokomiso (96/127) Jul 03 2003 Hi,
- Daniel Yokomiso (96/127) Jul 03 2003 Hi,
- Daniel Yokomiso (6/6) Jul 03 2003 Sorry for the duplicated post. I cancelled (in my client) before sending...
- Walter (26/142) Jul 07 2003 are
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
"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 templatedesignwon'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 waytoname 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 itwekludge 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 inDwe 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
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 thanreal -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]intemplate 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 currentlyDThere 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/2003we 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 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 thanreal -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]intemplate 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 currentlyDThere 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/2003we 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 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
Jul 03 2003
"Daniel Yokomiso" <daniel_yokomiso yahoo.com.br> wrote in message news:be0v2n$22u1$1 digitaldaemon.com...----- Original Message ----- From: "Walter" <walter digitalmars.com>are"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 templatesthirdalways "more specialized" than the base templates. For example, theuncompiledone above wouldn't work because complex is not more specialized thanreal -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 postcode). 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,inothers we defined traits classes implementing the interface and then intheclient 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]mul(instancetemplate 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).QuantitydependentinTUnit(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 currentlyDwe don't have dependent template types.I see what you're trying to do here, but I'm not quite sure whatbetemplate types are <g>. The above should work if C1, G1, and S1 are constants, but it's possible that then the instatiation of TUnit wouldbutinfinite 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 typesof'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(templateTUnit(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 situationwouldbe 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'tberesolved 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 thatcovers99% 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, becausethisis 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,linearalgebra, 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