D - Type-safe varargs
- Richard Krehbiel (62/62) Nov 13 2003 Seeing recent discussions like "formatted output trial balloon", "stream...
- Roberto Mariottini (33/95) Nov 14 2003 Hi,
- Andy Friesen (22/64) Nov 14 2003 Clever idea, but it's not very efficient, as it involves the compiler
- J Anderson (51/82) Nov 14 2003 A slightly different idea from the above. What about using overloaded
Seeing recent discussions like "formatted output trial balloon", "stream
operator suggestions", "printf and read/write a proposal", etc., I'm
encouraged to post my proposal for type-safe varargs again - since I
still think it's a Good Idea.
Here's an example of the syntax for a type-safe varadic function:
int print(PrintParam args...)
What this says is that the function "print" takes a variable number of
arguments, and each is of type "PrintParam", and they have been
collected into a dynamic array "PrintParam args[]".
So imagine we have:
class PrintParam
{
this(char[] arg) { /* build a PrintParam from a string */ }
this(int arg) { /* build a PrintParam from an int */ }
/* etc, ad infinitum */
...then construction of this array is done automatically by the compiler
when the user invokes the function "print", something like this:
int d = 2;
print("The value of D is ", d, "\n");
(Oh, by the way: Notice that this is the Perfect Syntax for a print
function - just arguments, separated by commas.)
...then the compiler produces code equivalent to this:
PrintParam _t[];
_t[0] = new PrintParam("The value of D is ");
_t[1] = new PrintParam(d);
_t[2] = new PrintParam("\n");
print(_t);
The compiler coerces each argument into the proper type, in this case
PrintParam. It does so by looking for a constructor which can be used
to build a PrintParam (if the argument isn't already compatible with
PrintParam, that is).
You can imagine each PrintParam constructor collecting it's arg into
something variant-like, or performing the conversion to string, or
something else.
You can also imagine formatters derived from PrintParam:
class WidthParam : PrintParam
{ this(int w) { /* whatever */ }
}
WidthParam width(int w)
{
return new WidthParam(w);
}
print(width(16), var, "\n");
The "width" function returns a WidthParam, which is compatible with
PrintParam because it's a derived class, and so is already an acceptable
parameter; nothing need be automatically constructed. Now, while
"print" is walking it's argument array, it can see if any is a
WidthParam, and so apply the modifier to the next output operation.
To make an object printable, add a method that returns a PrintParam
constructed from it:
class MyWeirdObject
{
PrintParam toPrint() { /* ... */ }
...
And then, call it:
print(myWeirdo.toPrint());
Sadly, I've not been able to figure out how to make your own objects
implicitly printable (as in "print(myWeirdo)"), unless they're derived
from PrintParam, without adding something else to the language, either
multiple inheritance (PrintParam can't be an interface, because it must
be directly constructible) or type-cast operator overloads (defining
method "operator PrintParam()" in your class).
Nov 13 2003
Hi,
Comments following.
In article <bp0bie$uvn$1 digitaldaemon.com>, Richard Krehbiel says...
Seeing recent discussions like "formatted output trial balloon", "stream
operator suggestions", "printf and read/write a proposal", etc., I'm
encouraged to post my proposal for type-safe varargs again - since I
still think it's a Good Idea.
Here's an example of the syntax for a type-safe varadic function:
int print(PrintParam args...)
What this says is that the function "print" takes a variable number of
arguments, and each is of type "PrintParam", and they have been
collected into a dynamic array "PrintParam args[]".
So imagine we have:
class PrintParam
{
this(char[] arg) { /* build a PrintParam from a string */ }
this(int arg) { /* build a PrintParam from an int */ }
/* etc, ad infinitum */
...then construction of this array is done automatically by the compiler
when the user invokes the function "print", something like this:
int d = 2;
print("The value of D is ", d, "\n");
(Oh, by the way: Notice that this is the Perfect Syntax for a print
function - just arguments, separated by commas.)
...then the compiler produces code equivalent to this:
PrintParam _t[];
_t[0] = new PrintParam("The value of D is ");
_t[1] = new PrintParam(d);
_t[2] = new PrintParam("\n");
print(_t);
The compiler coerces each argument into the proper type, in this case
PrintParam. It does so by looking for a constructor which can be used
to build a PrintParam (if the argument isn't already compatible with
PrintParam, that is).
You can imagine each PrintParam constructor collecting it's arg into
something variant-like, or performing the conversion to string, or
something else.
You can also imagine formatters derived from PrintParam:
class WidthParam : PrintParam
{ this(int w) { /* whatever */ }
}
WidthParam width(int w)
{
return new WidthParam(w);
}
print(width(16), var, "\n");
The "width" function returns a WidthParam, which is compatible with
PrintParam because it's a derived class, and so is already an acceptable
parameter; nothing need be automatically constructed. Now, while
"print" is walking it's argument array, it can see if any is a
WidthParam, and so apply the modifier to the next output operation.
To make an object printable, add a method that returns a PrintParam
constructed from it:
class MyWeirdObject
{
PrintParam toPrint() { /* ... */ }
...
And then, call it:
print(myWeirdo.toPrint());
Sadly, I've not been able to figure out how to make your own objects
implicitly printable (as in "print(myWeirdo)"), unless they're derived
from PrintParam, without adding something else to the language, either
multiple inheritance (PrintParam can't be an interface, because it must
be directly constructible) or type-cast operator overloads (defining
method "operator PrintParam()" in your class).
Why not adding some intelligence to the compiler?
int print(..., Printable[] args, DefaultPrintable)
Here Printable is an interface, and the compiler does this:
for each parameter Pi
{
if Pi is a Printable
then args[i] = Pi
else args[i] = new DefaultPrintable(Pi)
}
So the example:
MyClass d = something(); // MyClass is a Printable
print("The value is: ", d, endl);
becomes:
Printable _t[];
_t[0] = new DefaultPrintable("The value is: "); // char[] is not a Printable
_t[1] = d; // d is a Printable
_t[2] = endl; // endl is a Printable
print(_t);
Here DefaultPrintable is a class that implements the Printable interface,
with the same functionality as your PrintParam class, and endl is a constant
object of a class that implements Printable.
You can have also a PrintParam, interface that extends Printable, used by
print to modify the aspect of the next parameter to print.
Still I don't know what there should be inside the Printable interface:
interface Printable
{
???
}
And how print() can print an array of Printables?
Ciao
Nov 14 2003
Roberto Mariottini wrote:
Why not adding some intelligence to the compiler?
int print(..., Printable[] args, DefaultPrintable)
Here Printable is an interface, and the compiler does this:
for each parameter Pi
{
if Pi is a Printable
then args[i] = Pi
else args[i] = new DefaultPrintable(Pi)
}
So the example:
MyClass d = something(); // MyClass is a Printable
print("The value is: ", d, endl);
becomes:
Printable _t[];
_t[0] = new DefaultPrintable("The value is: "); // char[] is not a Printable
_t[1] = d; // d is a Printable
_t[2] = endl; // endl is a Printable
print(_t);
Here DefaultPrintable is a class that implements the Printable interface,
with the same functionality as your PrintParam class, and endl is a constant
object of a class that implements Printable.
You can have also a PrintParam, interface that extends Printable, used by
print to modify the aspect of the next parameter to print.
Still I don't know what there should be inside the Printable interface:
interface Printable
{
???
}
And how print() can print an array of Printables?
Ciao
Clever idea, but it's not very efficient, as it involves the compiler
allocating an interface for every argument passed, whether or not it's a
PDT.
Something else that would cut it is some sort of template list where
each element has its own type. (think C++ typelist) Then you could
recursively go through the list, and let template specialization figure
out what to call for each argument. Obviously, D templates aren't yet
up to this task.
Barring that, it would be quite useful if it were possible to have a
function that accepts any number of arguments (all of the same type),
and pass them as an array. ie
void foo(char[] s, params int[] args) { ... }
...
foo("Hurrah", 5, 2, 8, 9, 7, ...);
Of course, this is nearly useless in the case of string formatting (too
many toString calls needed), but it would still be useful in a great
number of other situations. Further, even when writing/using a
formatted printing function, params Object[] would be nearly ideal.
(except for the problem of dumping PDTs in pointless wrapper classes, a
la Java)
-- andy
Nov 14 2003
Richard Krehbiel wrote:
Seeing recent discussions like "formatted output trial balloon",
"stream operator suggestions", "printf and read/write a proposal",
etc., I'm encouraged to post my proposal for type-safe varargs again -
since I still think it's a Good Idea.
Here's an example of the syntax for a type-safe varadic function:
int print(PrintParam args...)
What this says is that the function "print" takes a variable number of
arguments, and each is of type "PrintParam", and they have been
collected into a dynamic array "PrintParam args[]".
So imagine we have:
class PrintParam
{
this(char[] arg) { /* build a PrintParam from a string */ }
this(int arg) { /* build a PrintParam from an int */ }
/* etc, ad infinitum */
....then construction of this array is done automatically by the
compiler when the user invokes the function "print", something like this:
int d = 2;
print("The value of D is ", d, "\n");
(Oh, by the way: Notice that this is the Perfect Syntax for a print
function - just arguments, separated by commas.)
....then the compiler produces code equivalent to this:
PrintParam _t[];
_t[0] = new PrintParam("The value of D is ");
_t[1] = new PrintParam(d);
_t[2] = new PrintParam("\n");
print(_t);
The compiler coerces each argument into the proper type, in this case
PrintParam. It does so by looking for a constructor which can be used
to build a PrintParam (if the argument isn't already compatible with
PrintParam, that is).
A slightly different idea from the above. What about using overloaded
member functions instead of constructors. ie
class M {}
class G {}
class X
{
void print(char* [] cov ...) {[code]} //Char* [] is a list of converted
values //cov is a list as well as the function's name
char* cov(float foo) {[code]} //Converts float to char*
char* cov(int foo) {[code]} //Converts int to char*
char* cov(X foo) {[code]} //Converts class X to char*
char* cov(int foo, float foo) {[code]} //Idea 2 - int followed by a
float (advanced use - may be to hard for the complier to figure out)
void print(M [] cov2 ...) {[code]} //Idea 3 - Double overload - Again
may be to difficult for complier to check for double overloads
M cov2(G foo); //Converts G to M
M cov2(char*); //Double overload - would cause
problem
}
Calling would be like:
X bar = new X;
bar.print("hi", 10.4f, 10);
Which would be equivilant to:
X bar = new X;
bar.print("hi", bar.cov(10.4f), bar.cov(10));
-----------------
and (Idea 2):
bar.print("hi", 2, 2.5, 4);
would be
bar.print("hi", bar.cov(2, 2.5), bar.cov(4));
-----------------
and (Idea 3):
G bar2 = new G;
G bar3 = new G;
bar.print(bar2, bar3);
would be
bar.print(bar.cov2(bar2), bar.cov2(bar3)); //Calls the void print(M
[] list ...) version of print
Furthermore if there is not bar.print alternative in the current class
then it could check the input object param (verging on copy
constructors) ie:
class H
{
char* cov() {[Code]}
}
...
bar.print(H);
would convert to:
bar.print(H.cov());
-Anderson
Nov 14 2003









Andy Friesen <andy ikagames.com> 