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 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
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 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
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









"Daniel Yokomiso" <daniel_yokomiso yahoo.com.br> 