equivariant functions

Steven Schveighoffer schveiguy at yahoo.com
Wed Oct 15 05:52:54 PDT 2008


"Sergey Gromov" wrote
> Tue, 14 Oct 2008 16:31:15 -0400,
> Steven Schveighoffer wrote:
>> Damn, I think there is some confusion here, because we are trying to 
>> solve
>> two related, but unequal problems.
>>
>> First is const equivariance (is that the right term?).  I want to specify 
>> a
>> function that treats an argument as const, but does not affect the const
>> contract that the caller has with that argument (i.e. my original scoped
>> const proposal).
>>
>> The second is type equivariance.  I want to specify that I will treat an
>> argument as a base type, but I will not change the derived type that the
>> caller is using.
>>
>> These are similar, if you consider that const is a 'base type' of its
>> mutable or invariant version.  But there is one caveat that is hard to
>> explain using this terminology.  const is a type modifier, not a type. 
>> So
>> it's not really a base type of a type, and it can be applied to any type 
>> in
>> existance.  Not the same as specifying a base type.
>
> This is a very good point.  I think these cases are different, too.  And
> I think there is another confusion which leads to obscure syntaxes.
>
> Any function argument has two types: the call-site and the function-site,
> the latter is made from the former by an implicit cast.  You must specify
> function-site type for the function body to compile properly, and you
> want to know a call-site type for equi-trickery.
>
> But that's not all.  An equi-function should have two return types: one
> function-site for the body to compile properly, and one call-site for the
> type system to work there.
>
> Let's experiment with an explicit syntax.  I'll define a calltype()
> built-in function similar to typeof() but yielding a call-site type in a
> function definition context.  And I'll use cast() to specify the call-
> site return type of a function.
>
> cast(calltype(s)) const(char)[] stripl(const(char)[] s);
>
> Here cast() explicitly defines this function as a variant function.
>
> class A { cast(calltype(this)) A clone() { return new A; } }
>
> Now a verbose one:
>
> cast(PassQual!(calltype(a), calltype(b))(char)[])
>  const(char)[] choose(const(char)[] a, const(char)[] b, bool which);
>
> Now that I look at this, all the magic is concentrated within the cast()
> block and is actually quite close to Bill Baxter's typedef {} proposal.
> And it seems like the call-site types are only needed there, and they're
> the only types needed.  So typeof() can be used there with modified
> semantics.  I even think that inout() can be used instead of cast() with
> pretty much the same effect on readability.  So a simple covariant
> function would look like this:
>
> inout(typeof(s)) const(char)[] foo(const(char)[] s)
> {
>  return s[1..$];
> }
>
> which is translated into:
>
> T foo(T)(T s)
> {
>  return cast(T) _foo_impl(s);
> }
> const(char)[] _foo_impl(const(char)[] s)
> {
>  return s[1..$];
> }
>
> Though I'd prefer cast() or var() or covar() as a covariant modifier.

Auto-deducing the return type is only half the problem.

During the function, the compiler must make certain guarantees about s, and 
about the return type.  For example, something cannot be implicitly cast to 
the type of s, since its true type is not known at compile-time, only it's 
base type.  And since the return type must match the constancy of the input, 
the return type must be restricted to only the type of s.  Since you can't 
implicitly cast to s, you must return s or a subset of s.

But the return type doesn't necessarily have to be related to s, it could be 
just of the same constancy.  Someone asked, what if you are returning a 
private field (i.e. through an accessor).  Would you use 
typeof(this.privateField) in the signature?  Doesn't this expose some 
private field name?  What if there was no field that existed for the type 
you wanted to return?  You'd have to do kludgy stuff like typeof(T.init), 
but then how do you flag the argument as being const-variant?

I think we need something to flag the argument as const-variant, and 
something to flag the return value as const-variant.  It's clearer that the 
argument is being affected, and it's clearer what you want to return.  To 
me:

inout(char)[] stripl(inout(char)[] s);

is much clearer than

typeof(s) stripl(const(char)[] s);

I can look at each part of the signature, and know exactly what is going on. 
With the second syntax, everything has references to other pieces, so I have 
to look back and forth to see what is happening.  If s is buried in a long 
list of arguments, it's even less clear.

Note also that the typeof(T) syntax does not help functions with multiple 
arguments without template kludginess (and I can't see how the compiler 
enforces the guarantees, given a custom template), whereas using a builtin 
type modifier handles it perfectly.

At least for const-variance, a type modifier is ideal.  Some of the other 
ideas might benefit from a typeof (arg) syntax.

-Steve 





More information about the Digitalmars-d mailing list