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