Generic programming ramifications of const by default

Reiner Pope some at address.com
Sun Jun 10 19:30:55 PDT 2007


Bill Baxter wrote:
> Walter Bright wrote:
>> Bill Baxter wrote:
>>> * "having same type defaults is better for generics"
>>>   (maybe...but I'm not convinced. If you have powerful enough 
>>> metaprogramming it shouldn't be hard to strip const from a type 
>>> tuple, or add it.  And people do far more programming than 
>>> meta-programming.)
>>
>> This is a good point. With const-by-default, you have function type 
>> declarations behaving *fundamentally* different from other 
>> declarations. Given the metaprogramming ability to use tuples to 
>> declare parameters, doing function template type deduction for 
>> parameters, and type inference on parameters, making this 
>> fundamentally different may wind up really screwing things up.
> 
> Yes, it's a good point (it's David Held's point), but again your 
> argument is "may wind up really screwing things up".  On the other hand 
> it may wind up *not* screwing things up.  Gut feelings are great, but 
> neither you nor David have given any concrete examples.
> 
> So let's look at a callback library like std.signals.
> 
> With std.signals we do something like:
> 
>     mixin Signal!(char[], int);
> 
> And the call signature for slots becomes
> 
>     void delegate(char[],int).
> 
> To get only requires a simple variadic tuple parameter:
> 
> template Signal(T1...)
> {
>     alias void delegate(T1) slot_t;
>     . . .
> }
> 
> With const default it will be a little more annoying because it doesn't 
> make sense to have const be the default for template paramters.  So the 
> user would need to say something like:
>     mixin Signal!(in char[], in int);
> But if the only transformation is stickig 'in' on everything a template 
> can be made to do that. So just something like...
>     mixin Signal!(ParameterTuple(char[], int));
> 
> But what if the user actually wants some parameter to be mutable? Like a 
> ref int instead of an int.  There would have to be some keyword to mean 
> 'make it mutable' in any event for const default.  So just make our 
> ParameterTuple metafunction smart enough not to override that.  Maybe 
> keep inout for this:
>     mixin Signal!(ParameterTuple(char[], inout int));
> 
> And rememeber that signals are *used* much more frequently than they are 
> defined.  Users of the signal won't have to think about it, they'll just 
> write their function:
>     void cb(char[], inout int) { . . . }
> and it'll work.
> 
> So that's a realistic case of wanting to take a regular tuple, and 
> needing it to be a parameter tuple.  And it doesn't look like it would 
> really screw things up so far.
I agree with your example, although I would think it makes sense to put 
the ParameterTuple!() call inside the template, not at the call-site. As 
I argue further down, I actually think it should be part of the language 
core.

I agree with what you have written. I've got another similar example: 
the hypothetical DeclareFunction template, to be used as:

alias DeclareFunction!(int, "a", double, "d", "code") myFunction;
// should be equivalent to
// void myFunction(int a, double d) { code }
// no matter if const-by-default or not

Clearly in this example, the ParameterTuple!() call should be done 
within the template, not at the call-site. I would argue that this is 
the same for std.signals: the declaration syntax looks like that of a 
function, so you should be able to declare types like a function. But 
that's fine, we can manage that inside our template.

In fact, many examples I can think of with tuples deserve the same 
treatment, because you are effectively defining the parameters of a 
function, so you want them to look like that.

---

I think another problem arises with compile-time variadic functions:

printf(T...)(T t)

Walter raised a concern about type inference and const-by-default; 
perhaps he was referring to functions like this.

But suppose that const wasn't default. Since printf doesn't modify its 
arguments, you should really write:

printf(T...)(const(T) t) // or MakeEachTypeConst!(T) t

so type deduction presents the same problem here either way.

---

I've got my own thoughts on how to set up const-by-default, which are 
very similar to what you suggested with 'inout' and ParameterTuple!(). 
The main difference I suggest is making it language-supported, which 
allows for more type inference as well as a better abstraction for 
tuples. There are three basic details, and one little detail about 
compile-time variadic functions:

1. ---- Addition of mutable() type constructor to language.
(Here, mutable(T) is effectively equivalent to inout(T) as you described 
it. However, I don't like the idea of overloading inout's meaning, and I 
think mutable is a more appropriate name).

In addition to the type constructors, const(), invariant(), etc, add also

     mutable(T)

which behaves identically to a non-const T (ie each is implicitly 
convertible to the other).

Its importance lies in the fact that:

     const(mutable(T)) == mutable(T)

whereas

     const(T) == const(T) otherwise

This effectively allows you to describe three kinds of (parameter) types:
    mutable -- "this will be mutable no matter what"
    const -- "this will be const no matter what"
    <modifierless> -- "I'll let you, the library implementer, choose 
appropriately"

2. ---- Make type constructors apply recursively to type tuples

In addition to this, if we have

    alias Tuple!(stuff) T;
then

    const(T) == Tuple!( const(T[0]), const(T[1]), ...)

and similarly for all the other type constructors.

3. ---- All function parameters (including tuple parameters) have an 
implicit const()

This is the const-by-default bit. All function parameters have an 
implicit const() wrapped around the type. Mutable parameters then look 
like this:

     void foo ( mutable(Bar) ) {...}

and inside the compiler we get

    mutable(Bar) ----->    const(mutable(Bar)) ----->    mutable(Bar)
   (as it is declared)   (add the implicit const())    (apply the 
simplification rule)

(Little detail)
4. ---- How compile-time variadic functions have IFTI
The tuple is filled with the types from the call-site. Most of these 
will be unornamented, giving the function-writer the freedom to make 
them const or mutable as he/she chooses:

   printf(T...)(T t) {...} // remember the implicit const()

...

const(char[]) a;
char[] b;
printf(3, b, "abc", a);
// In printf, T is now  Tuple!(int, char[], invariant(char[]), const(char[])
// and typeof(t) == typeof(const(T))

-----



The beauty is that this works exactly as you would expect for 
signal&slots: in fact, no change in the library is required for it to 
support const-by-default.

If you wanted to write a 'retro' signal&slots library which *didn't* 
have const-by-default, you could do that, too:

template Signal(T1...)
{
   alias void delegate(mutable(T1)) slot_t;
   ...
}

And, whereas details 1-3 could be done with template magic, supporting 
it in the language allows IFTI for compile-time variadic functions (like 
printf).

> 
> 
> But how about the other way?  Say now we extract a parameter tuple from 
> a function and we want to make a value tuple out of it we can use to 
> call the function.
> void call(slot_t someslot) {
>    alias  std.traits.ParameterTypeTuple(slot_t) ArgTup;
>    ArgTup args;
>    args[0] = "Hi there";
>    args[1] = 5;
>    someslot(args);
> }
> 
> That won't work if slot_t's argument type tuple is all "constipated".
> But this should be doable too, I would think.  Just something like a 
> ValueTypeTuple(ArgTup).
> 
> But even if that's not possible, you're going to have the _exact_ same 
> problem with any form of const when trying to convert an argument tuple 
> into a tuple you can make a variable out of.  So it's hardly a strike 
> against const by default.

I think this is an unlikely usage, and the problem is presented with and 
without const-by-default. To get rid of *all* the consts, you would 
probably have to do some kind of type introspection with lots of 
recursive static if (is T : const(U)) expressions, or something similar.

   -- Reiner



More information about the Digitalmars-d mailing list