www.digitalmars.com         C & C++   DMDScript  

D - Type-safe varargs

reply Richard Krehbiel <rich kastle.com> writes:
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
next sibling parent reply Roberto Mariottini <Roberto_member pathlink.com> writes:
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
parent Andy Friesen <andy ikagames.com> writes:
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
prev sibling parent J Anderson <REMOVEanderson badmama.com.au> writes:
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).

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