May 06, 2003
How about this:

If you declare a list, the compiler knows exactly what's in it because it parsed it and used type inference.

But if you pass that list to a variable argument function, the compiler must supply enough type info that the function knows what's in the list.

The compiler could auto-generate this parallel list of type info's.

Once the function has this list, and the corresponding type info's, (which should be wrapped transparently into one structure, the variable argument list) it can iterate thru the list and dispatch based on the type info to whatever registered functions it has for the type.

You could register any type (think:  formatter functions)

If you want real generic support you want a way to auto-register types.

This is where an extension to the overloading mechanism would come in.

Overload the parameters with *runtime dispatch*.

That means if you call a function on a type that is unknown at compile time, it figures out what type it is at runtime and calls the appropriate functions (think:  switch/call or some kind of map of function pointers).

Then just by providing a global overload for "write into stream" you could get any class to be able to be written into any stream.

void write(stream s, int t) { s.write(t.tostring()); }
void write(stream s, string t) { s.write(t); }
void write(stream s, mytype t) { s.write(t.tostring()); }

void print(... args)
{
    for(x in args)
    {
        write(consoleoutstream, x); // runtime dispatch on type of x
    }
}

int main()
{
    print(1, "+", 2, "=", 1+2);
    mytype a;
    print("a is", a);
    return 1;
}

This is totally analogous to the way printf works, but modernized and *type safe*.  And if varargs wasn't just for args, but was yet another basic type, compatible with user-defined lists, it could prove extremely useful in other situations as well.

Think what you could do if you add this to the above:

void write<type T> (stream s, T t) { s.write(t.tostring()); }  // that's
newfangled template syntax, not D.  D's template scope would cause namespace
lookup and overloading issues

One problem might be how is the print function above (in some module A) supposed to know about write functions declared in some other module B? Overloading wouldn't work right.

Oh well.

Sean

"Helmut Leitner" <helmut.leitner@chello.at> wrote in message news:3EB74DD5.53F278D6@chello.at...
>
>
> "Sean L. Palmer" wrote:
> >
> > So I have to provide all those functions (including gets?) just to get something to print out?  It seems a bit cumbersome.
>
> It is cumbersome.
>
> > This might get a bit
> > better once D's builtin typeinfo's improve.
>
> I did it because it shows why type abstraction is needed and what it has to replace.
>
> > The fact that you have to override both Print and StrCat indicates that something is amiss in the design.  What about fprintf?  What if I want
to
> > print into a stream or i/o device of my own design?
>
> I'm not a lover of this Java stuff. Once you start it, It's hard to get it perfectly right. Currently there is a heavy Stream class in Phobos. But to have the kind of extensibility you are looking for, a Stream should maybe be a lightweight interface. One could then have the Console as a redirectable default stream and one could implement it on any object without inheriting from the Stream class.
>
> > What if I want to print
> > into a socket or mailslot or named pipe?  How do I do all that without
> > having a proliferation of functions?
>
> No, these are only meant for primitive types and they will surely be replaced by some internal mechanism sooner or later.
>
> On the other hand I can't see a difference between an ugly proliferation of functions and the typical ugly proliferation of classes and methods.
>
> > Your wrappers may be efficient, but they're still wrapping sprintf,
which is
> > a very weighty function.
>
> One could replace it by basic functions. I didn't because I would have had to write a lot of code for this. My main goal was to have a simple way to write interfaces that work with any primitive type.
>
> BTW sprintf is weighty but also efficient. If you don't ban it completely
from
> your application you will carry its weight around. But if it's there, why
not
> use it?
>
> > C++ iostreams got it right.  They totally separate the stream
buffer/device
> > from the stream formatting/insertion/extraction.  That way is so much
more
> > flexible.
>
> But this IO is very costly and adds a lot to the C++ bloat. It will be interesting to see how D can handle this.
>
> --
> Helmut Leitner    leitner@hls.via.at
> Graz, Austria   www.hls-software.com


May 06, 2003
"Sean L. Palmer" <palmer.sean@verizon.net> escribió en el mensaje
news:b97q8f$27if$1@digitaldaemon.com...
| ...
|
| void write(stream s, int t) { s.write(t.tostring()); }
| void write(stream s, string t) { s.write(t); }
| void write(stream s, mytype t) { s.write(t.tostring()); }
|
| void print(... args)
| {
|     for(x in args)
|     {
|         write(consoleoutstream, x); // runtime dispatch on type of x
|     }
| }
|
| ...

The more I think about it, the more I like it. However (correct me if I'm
wrong) x needs to be declared somewhere. And that's a problem because args
could contain (following your example) int, string, or mytype. Now, how do
you declare x? You can't declare it as object, because it will complain when
there's an int, float, or whatever. That wouldn't be a problem if D did some
boxing/unboxing a la .NET (is that so or did I mix up some terms?). I mean,
if D allowed us to do:
void foo(object o) { ... }
...
int i;
foo(i);
...
Or the opposite, for instance. That would be awesome. I think there've been
arguments against it before, but now we can see where it can be useful. Even
more: I remember someone asking toString() for all the basic types. Well,
with this, and when toString() gets implemented, you could do this:
void write(stream s, object o) { s.write(o.tostring()); }
And you wouldn't need to provide any other function. I don't know if all
this is desirable or not, but at least I think it would be useful.

————————————————————————— Carlos Santander


---
Outgoing mail is certified Virus Free.
Checked by AVG anti-virus system (http://www.grisoft.com).
Version: 6.0.476 / Virus Database: 273 - Release Date: 2003-04-24


May 06, 2003
x would be a special case because it's an iterator used on a varargs list; it'll have various types throughout the iteration.  Pretty much any type that can show up there (that a valid overload exists for write, maybe) will get its own version of the loop body, customized for that type.

Sean

"Carlos Santander B." <carlos8294@msn.com> wrote in message news:b98dvl$2p4g$1@digitaldaemon.com...
> "Sean L. Palmer" <palmer.sean@verizon.net> escribió en el mensaje
> news:b97q8f$27if$1@digitaldaemon.com...
> | ...
> |
> | void write(stream s, int t) { s.write(t.tostring()); }
> | void write(stream s, string t) { s.write(t); }
> | void write(stream s, mytype t) { s.write(t.tostring()); }
> |
> | void print(... args)
> | {
> |     for(x in args)
> |     {
> |         write(consoleoutstream, x); // runtime dispatch on type of x
> |     }
> | }
> |
> | ...
>
> The more I think about it, the more I like it. However (correct me if I'm wrong) x needs to be declared somewhere. And that's a problem because args could contain (following your example) int, string, or mytype. Now, how do you declare x? You can't declare it as object, because it will complain
when
> there's an int, float, or whatever. That wouldn't be a problem if D did
some
> boxing/unboxing a la .NET (is that so or did I mix up some terms?). I
mean,
> if D allowed us to do:
> void foo(object o) { ... }
> ...
> int i;
> foo(i);
> ...
> Or the opposite, for instance. That would be awesome. I think there've
been
> arguments against it before, but now we can see where it can be useful.
Even
> more: I remember someone asking toString() for all the basic types. Well,
> with this, and when toString() gets implemented, you could do this:
> void write(stream s, object o) { s.write(o.tostring()); }
> And you wouldn't need to provide any other function. I don't know if all
> this is desirable or not, but at least I think it would be useful.
>
> -------------------------
> Carlos Santander
>
>
> ---
> Outgoing mail is certified Virus Free.
> Checked by AVG anti-virus system (http://www.grisoft.com).
> Version: 6.0.476 / Virus Database: 273 - Release Date: 2003-04-24
>
>


May 06, 2003
I think you are absolutely right.

The best of this kind is in VB (really!!!). You define a function IIRC like

  foo(int x,int y,ParamArray pa)

and you can call it this way

  foo(x,y,1,"xy",-13.5)

Within foo you can query the array size, you have the "Variant" type that may hold any other simple type, you can query the type of each array element and do any conversion you need...

"Sean L. Palmer" wrote:
> 
> How about this:
> 
> If you declare a list, the compiler knows exactly what's in it because it parsed it and used type inference.
> 
> But if you pass that list to a variable argument function, the compiler must supply enough type info that the function knows what's in the list.
> 
> The compiler could auto-generate this parallel list of type info's.
> 
> Once the function has this list, and the corresponding type info's, (which should be wrapped transparently into one structure, the variable argument list) it can iterate thru the list and dispatch based on the type info to whatever registered functions it has for the type.
> 
> You could register any type (think:  formatter functions)
> 
> If you want real generic support you want a way to auto-register types.
> 
> This is where an extension to the overloading mechanism would come in.
> 
> Overload the parameters with *runtime dispatch*.
> 
> That means if you call a function on a type that is unknown at compile time, it figures out what type it is at runtime and calls the appropriate functions (think:  switch/call or some kind of map of function pointers).
> 
> Then just by providing a global overload for "write into stream" you could get any class to be able to be written into any stream.
> 
> void write(stream s, int t) { s.write(t.tostring()); }
> void write(stream s, string t) { s.write(t); }
> void write(stream s, mytype t) { s.write(t.tostring()); }
> 
> void print(... args)
> {
>     for(x in args)
>     {
>         write(consoleoutstream, x); // runtime dispatch on type of x
>     }
> }
> 
> int main()
> {
>     print(1, "+", 2, "=", 1+2);
>     mytype a;
>     print("a is", a);
>     return 1;
> }
> 
> This is totally analogous to the way printf works, but modernized and *type safe*.  And if varargs wasn't just for args, but was yet another basic type, compatible with user-defined lists, it could prove extremely useful in other situations as well.
> 
> Think what you could do if you add this to the above:
> 
> void write<type T> (stream s, T t) { s.write(t.tostring()); }  // that's
> newfangled template syntax, not D.  D's template scope would cause namespace
> lookup and overloading issues
> 
> One problem might be how is the print function above (in some module A) supposed to know about write functions declared in some other module B? Overloading wouldn't work right.
> 
> Oh well.
> 
> Sean

--
Helmut Leitner    leitner@hls.via.at Graz, Austria   www.hls-software.com
May 06, 2003
I'm not sure i know what varargs are for.

* They requiere you to explicitly - or implicitly secify a number of parameters beforehand

* They spoil the calling convention.

* They are by definition error-prone.

In C, you couldn't define an array right in your code, so the varargs jumped in.

What you are used to looks like this:

   printf ("format %s %d", s, b);

is it really worse than:

   printf ("format %s %d", [s,b]);

And even if you think it is, if a function expects an array, why can't you make the compiler turn the first into the second one? It would also solve the problem of "how do i write a varargs function in an easy and elegant manner". Either it is a relative of a printf or some other array or varargs function - so it gets an array and passes it further. Or it is a completely new beast - then it simply iterates through an array with standard means. And with all that: the PASCAL-like calling convention can be maintained, with called function doing the cleanup.

You might take a look at OpenGL. Yes, it *can* use varargs - but then you need to specify number of arguments explicitly. In D, where dynamic arrays are a part of the language, this is not requered any longer.

DUMP VARARGS! They have *really* nothing to do in D. Instead, you can provide an alternative calling syntax for functions accepting arrays - maybe other agregates as well. I believe some new agregates could be useful, like tuples named here -- maybe even lists...

Further comments are embedded...

Sean L. Palmer wrote:
> Then stop and think:  Why should varargs be limited only to args?  Why not
> make them a basic type, so they can be stored, created, manipulated kinda
> like an array?

Why not use an array? Varargs have never actually been for accepting different types. They are a hack, based on rudimentary C's way of handling functions without prototypes. Later, as prototypes became a requierement, a way had to be found to formalise this nonsense.

Mind that you actually have to give printf *manually* all argument types in the format string?

> The difference between a variable argument list and a struct initializer is
> that a struct has curly braces around it, and has a name, and its fields
> have names.
> Variable parameter lists are the same thing but with parenthesis, and
> inferred type.
--- 8< -/- >8 ---

That would be a tuple. :)
A useful type all by itself. I can summarise the way Sather handles tuples and console io stuff later. I know it's not state-of-the-art, but i find it appropriate for the OO environment - which is what we have here.

BTW, there is a problem with a function expecting a tuple - it has to know its exact type, else we're back to dealing with an array of generics.

> A)  Variably-typed parameters (single-argument varargs)
--- 8< -/- >8 ---

There is a slight problem with that.
It is possible in a true OO language like Sather - *everything* is a descendant of the root object, even INT and such.
Note that it doesn't mean that Sather attaches a vtable to all types - it does only to those handled by reference. It also allows types handled by value, stack-allocated, which don't get a vtable ptr. They only get it when passed where an abstract object is expected - this is called boxing. :) And just like in D, abstract object defines conversion into a string. Sorry to say that, but D's OO seems like fairly half-hearted as compared to Sather.

> If you could put other things in them besides data, such as, say, types,
> well then you are approaching C++ in power.

If you get an abstract object, you don't have any problem since you can inspect its type. But in D, "lightweight" data types don't get boxed. This, BTW, is also a problem with unions - that they are typeless - would also get solved by boxing.

> Making varargs typesafe would make printf tolerable.  I still wouldn't like
> it, but I could tolerate it.  But I am not quite clear what all would be
> involved.  I know it would require direct compiler support.

There *is* a typesafe varargs printf out there. Take DLI and look in its library source. It is not included in the DMD's Version of Phobos. And take a look at "generic" type. D shows its glory sides in it. But one glory side more - and it were *much* simpler.

-i.

May 06, 2003
Putting in types would require essentially an array of TypeInfo pointers paralleling it.

"Sean L. Palmer" <palmer.sean@verizon.net> wrote in message news:b97f7e$1suu$1@digitaldaemon.com...
> For starters how about building variable parameter lists support into the language instead of living in <stdarg.h>
>
> They would need methods or properties to:
>
> Get the # of parameters
> Extract each parameter in a typesafe manner (applying automatic
conversions
> if necessary, perhaps)
>
> Iteration seems to be the key here.  The mechanism behind iteration
doesn't
> seem important;  whatever the rest of the language uses would be good. Nobody has come to a consensus about that yet?
>
> Then stop and think:  Why should varargs be limited only to args?  Why not make them a basic type, so they can be stored, created, manipulated kinda like an array?
>
> The difference between a variable argument list and a struct initializer
is
> that a struct has curly braces around it, and has a name, and its fields
> have names.
> Variable parameter lists are the same thing but with parenthesis, and
> inferred type.
>
> It's like constructing a struct from scratch, with no predetermined
layout.
> Blank slate.  Put anything you want in there.  And the fields don't have names, they only have order.
>
> In fact it's more like a block scope that can only contain data declarations, but without names.
>
> If you can declare those anywhere, and name, analyze and manipulate them anywhere, well, you have:
>
> A)  Variably-typed parameters (single-argument varargs)
> B)  Pairs/Tuples
> C)  Lists
> D)  Easy one-off structs, when you need a struct only once and don't want
to
> give it a name, or give its members names.
> E)  A lot of power
>
> If you could put other things in them besides data, such as, say, types, well then you are approaching C++ in power.
>
> Making varargs typesafe would make printf tolerable.  I still wouldn't
like
> it, but I could tolerate it.  But I am not quite clear what all would be involved.  I know it would require direct compiler support.


May 07, 2003
Of course, before anyone freaks out about making D more like VB, we would not use this all the time, but only for special situations like printf or when it's useful.  Not everything would be done as varargs.  There'd still be actual variables and exact parameter types if you wanted.  There'd still be arrays of uniform type.  Just adding ordered bags of datums of mixed types, and a bit of reflection to help figure out what's in them later.

I'm not sure I'd not rather have to specify:

foo(x, y, (1, "xy", -13.5) )

instead.  Or,

foo(x, y, [1, "xy", -13.5] )

Also Variant is kind of a nice way to write "void*" but it's kinda lame because it's so final.

I'd rather be able to constrain the types admitted somehow so they all have to derive off some base class or interface.  list of printables, list of streamables, maybe.  They don't have to be all the same type or size, and they wouldn't even really have to be references, they could be values such as structs or ints.  for instance, list of numerics.

Sean

"Helmut Leitner" <helmut.leitner@chello.at> wrote in message news:3EB7FD0A.8350CC5A@chello.at...
> I think you are absolutely right.
>
> The best of this kind is in VB (really!!!). You define a function IIRC
like
>
>   foo(int x,int y,ParamArray pa)
>
> and you can call it this way
>
>   foo(x,y,1,"xy",-13.5)
>
> Within foo you can query the array size, you have the "Variant" type that may hold any other simple type, you can query the type of each array
element
> and do any conversion you need...
>
> "Sean L. Palmer" wrote:
> >
> > How about this:
> >
> > If you declare a list, the compiler knows exactly what's in it because
it
> > parsed it and used type inference.
> >
> > But if you pass that list to a variable argument function, the compiler
must
> > supply enough type info that the function knows what's in the list.
> >
> > The compiler could auto-generate this parallel list of type info's.
> >
> > Once the function has this list, and the corresponding type info's,
(which
> > should be wrapped transparently into one structure, the variable
argument
> > list) it can iterate thru the list and dispatch based on the type info
to
> > whatever registered functions it has for the type.
> >
> > You could register any type (think:  formatter functions)
> >
> > If you want real generic support you want a way to auto-register types.
> >
> > This is where an extension to the overloading mechanism would come in.
> >
> > Overload the parameters with *runtime dispatch*.
> >
> > That means if you call a function on a type that is unknown at compile
time,
> > it figures out what type it is at runtime and calls the appropriate functions (think:  switch/call or some kind of map of function
pointers).
> >
> > Then just by providing a global overload for "write into stream" you
could
> > get any class to be able to be written into any stream.
> >
> > void write(stream s, int t) { s.write(t.tostring()); }
> > void write(stream s, string t) { s.write(t); }
> > void write(stream s, mytype t) { s.write(t.tostring()); }
> >
> > void print(... args)
> > {
> >     for(x in args)
> >     {
> >         write(consoleoutstream, x); // runtime dispatch on type of x
> >     }
> > }
> >
> > int main()
> > {
> >     print(1, "+", 2, "=", 1+2);
> >     mytype a;
> >     print("a is", a);
> >     return 1;
> > }
> >
> > This is totally analogous to the way printf works, but modernized and
*type
> > safe*.  And if varargs wasn't just for args, but was yet another basic
type,
> > compatible with user-defined lists, it could prove extremely useful in
other
> > situations as well.
> >
> > Think what you could do if you add this to the above:
> >
> > void write<type T> (stream s, T t) { s.write(t.tostring()); }  // that's
> > newfangled template syntax, not D.  D's template scope would cause
namespace
> > lookup and overloading issues
> >
> > One problem might be how is the print function above (in some module A) supposed to know about write functions declared in some other module B? Overloading wouldn't work right.
> >
> > Oh well.
> >
> > Sean


May 07, 2003
Walter wrote:
> "Sean L. Palmer" <palmer.sean@verizon.net> wrote in message
> news:b94ejo$20rf$1@digitaldaemon.com...
> 
>>const string endl = "\n";
>>long value = result();
>>print(just(right, 12)(hex(value)), " is the answer", endl);
>>
>>insert howevermany modifiers (such as just or hex above) here.  These
> 
> could
> 
>>be anything; they could be templates, whatever.  Very extensible.
> 
> 
> True, but I think you'll find that a lot of bloat is generated in the calls.

I've got an odd little philosophy regarding printf.

The C library printf function is like a little "virtual machine," whose "machine language" consists of the format string and the arguments. Broadly speaking, using a specialized VM gains you a lot of brevity, but has a performance const.  In the case of printf, the performance lost by interpreting the format string is massively overshadowed by the cost of the external I/O operations also involved, so it's a win.  I've got no problem so far.

But that still leaves the fact that C language users must code printf's "machine code" themselves, which is a source of many programming errors.

It sure would be nice to have some new compiler, which could create printf's machine language for me, one which would correctly match the number and types or the arguments to the format string.  (Hint, hint.)

May 07, 2003
In news://news.digitalmars.com/a7d9d2$qtv$1@digitaldaemon.com I wrote up a type-safe varargs proposal:


Start with the function declaration:

void printf(char[], PrintfParam args...)
{
// Function body


"type args..." is like "type args[]" but signals that the compiler should collect up the arguments into the array. "type... args" should be a synonym of "type args..." just as "type[] array" is the same as "type array[]". The body of this function treats args as if it were a regular dynamic array, because it is.

When a user codes:
    char[] name = "Cecil";
    printf("My name is %s\n", name);

..the compiler does the equivalent of this:

   PrintfParms[] _t;
   _t[0] = new PrintfParms(name);
   printf("My name is %s\n", _t);

Rules:

    Each function argument must be implicitly compatible with the argument type; else,

    Each argument must be convertible to the argument type via the type's constructor; else,

    The argument's not valid and the compiler prints an error.

Only the final parameter to a function can be varargs, just like C.

I like this better than the universal VARIANT type that VB uses, since that way allows programmers to pass types that the function doesn't expect, and/or requires the called function to expect every type.  The argument type only needs constructors for types it can deal with.

You can't make a varargs function in C which is allowed to take zero
arguments.  But with this method, you can:  void NoArgs(Type args...) could be called with no arguments, and would receive a zero-length array.

"<type> args..." is implicitly compatible with "<type> args[]" and, as such, can be passed around between functions, like C's va_list can.  But it's even better: you can write code to construct the a "<type> args[]", but you *can't* construct a va_list in C except by calling a varargs function.

Sean L. Palmer wrote:
> For starters how about building variable parameter lists support into the
> language instead of living in <stdarg.h>
> 
> They would need methods or properties to:
> 
> Get the # of parameters
> Extract each parameter in a typesafe manner (applying automatic conversions
> if necessary, perhaps)
> 
> Iteration seems to be the key here.  The mechanism behind iteration doesn't
> seem important;  whatever the rest of the language uses would be good.
> Nobody has come to a consensus about that yet?
> 
> Then stop and think:  Why should varargs be limited only to args?  Why not
> make them a basic type, so they can be stored, created, manipulated kinda
> like an array?
> 
> The difference between a variable argument list and a struct initializer is
> that a struct has curly braces around it, and has a name, and its fields
> have names.
> Variable parameter lists are the same thing but with parenthesis, and
> inferred type.
> 
> It's like constructing a struct from scratch, with no predetermined layout.
> Blank slate.  Put anything you want in there.  And the fields don't have
> names, they only have order.
> 
> In fact it's more like a block scope that can only contain data
> declarations, but without names.
> 
> If you can declare those anywhere, and name, analyze and manipulate them
> anywhere, well, you have:
> 
> A)  Variably-typed parameters (single-argument varargs)
> B)  Pairs/Tuples
> C)  Lists
> D)  Easy one-off structs, when you need a struct only once and don't want to
> give it a name, or give its members names.
> E)  A lot of power
> 
> If you could put other things in them besides data, such as, say, types,
> well then you are approaching C++ in power.
> 
> Making varargs typesafe would make printf tolerable.  I still wouldn't like
> it, but I could tolerate it.  But I am not quite clear what all would be
> involved.  I know it would require direct compiler support.
> 
> Sean
> 
> ----- Original Message -----
> From: "Walter" <walter@digitalmars.com>
> Newsgroups: D
> Sent: Sunday, May 04, 2003 9:17 PM
> Subject: Re: String formatting stuff
> 
> 
> 
>>That would be pretty cool. Any further ideas on it?
>>
>>"Sean L. Palmer" <palmer.sean@verizon.net> wrote in message
>>news:b94ebo$20n2$1@digitaldaemon.com...
>>
>>>printf takes a variable argument list as parameter.
>>>
>>>Make those variable argument lists first-class, with standardized means
> 
> of
> 
>>>manipulation, construction, and iteration.
>>>
>>>Would it be possible to make a printf that doesn't take a string as the
>>>first parameter?  That is what I desire:  A free-form mass of output
>>>capabilities that can apply however I wish without having to be known
>>>specifically at runtime or at compile time.  One that doesn't centralize
>>
>>the
>>
>>>processing of tokens like printf does.  One that is truly extensible by
>>>everybody.  You wouldn't want to clutter up other peoples' printf's,
>>
>>though
>>
>>>it'd be nice to import printf handlers from elsewhere kinda like a
> 
> module.
> 
>>>you could call it print.  It's like printf, but without the f.  In place
>>
>>of
>>
>>>the f, you have completely configurable output.
>>>
>>>throw in some parameterized field justification and precision control
>>
>>which
>>
>>>grab hold of a value and modify how it ends up appearing, by building an
>>>internal string of it and modifying that then transmitting it on.
>>>
>>>You could have a "shuffler" structure and reshuffle order of its
>>
>>parameters
>>
>>>based on some compile time switches or constants.
>>>
>>>List processing seems like the very act of iteration.
>>>
>>>Is this the glimmer of an answer to your iterator problems?
>>>
>>>Sean
> 
> 
> 
> 

May 07, 2003
Sean L. Palmer wrote:
> I'd rather be able to constrain the types admitted somehow so they
> all have to derive off some base class or interface.  list of
> printables, list of streamables, maybe.  They don't have to be all
> the same type or size, and they wouldn't even really have to be
> references, they could be values such as structs or ints.  for
> instance, list of numerics.

This rather seems a problem of partial OO in D.

In Sather, you could simply accept $OB and then do something like:

typecase some_arg
when INT then
   #OUT + "Got Integer: " + some_arg;
when FLT then
   #OUT + "Got Float: " + some_arg;
when $STR then
   #OUT + "Got String: " + some_arg;
end;

The typecase statement tries to cast the object to diffrent types and allows to do type-dependant actions. It may also contain an else-branch. If there is no else-branch and no match is found, an exception is rased analogous to the one the D cast would cause.

Please note, that INT, FLT, and such are value types - they have value semantics and are layed on the stack, as opposed to $STR and $OB - these are abstract types and are handled by reference. Only $-types have a VTable pointer. But nontheless, value types are descendants of $OB, and thus get *boxed* whenever you pass them and a reference to a abstract type is needed. BTW, you can see that you actually don't need to know a type for printing - because $OB defines a method for string conversion, like in D for reference objects.

Maybe *boxing* would be a solution.

But wait, hasn't the basic printf problem been solved in the DLI version of Phobos?

-i.

P.S. It may also be that Sather system could use a bit more abstraction - but this would be the issue of a library and not the language. I have to check this in-depth someday.