Thread overview | ||||||||
---|---|---|---|---|---|---|---|---|
|
July 02, 2003 templates issues (long) | ||||
---|---|---|---|---|
| ||||
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 Re: templates issues (long) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Daniel Yokomiso | "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 Re: templates issues (long) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter | 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 Re: templates issues (long) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter | 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 Re: templates issues (long) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Daniel Yokomiso | 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 Re: templates issues (long) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Daniel Yokomiso | "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! |
Copyright © 1999-2021 by the D Language Foundation