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