equivariant functions
Denis Koroskin
2korden at gmail.com
Tue Oct 14 17:38:17 PDT 2008
On Sun, 12 Oct 2008 23:34:05 +0400, Andrei Alexandrescu
<SeeWebsiteForEmail at erdani.org> wrote:
> Many functions return one of their parameters regardless of the way it
> was qualified.
>
> char[] stripl(char[] s);
> const(char)[] stripl(const(char)[] s);
> invariant(char)[] stripl(invariant(char)[] s);
>
> Stripl is not a particularly good example because it needs to work on
> wchar and dchar too, but let's ignore that aspect for now.
>
> There's been several proposals in this group on tackling that problem.
>
> In unrelated proposals and discussions, people mentioned the need for
> functions that return the exact type of this:
>
> class A { A clone(); }
> class B : A { B clone(); }
>
> How can we declare A.clone such that all of its derived classes have it
> return their own type?
>
> It took me a while to realize they are really very related. This is easy
> to figure out if you think that invariant(char)[] and char[] are
> subtypes of const(char)[]!
>
> I discussed with Walter a variant that implements equivariant functions
> without actually adding an explicit feature to the language. Consider:
>
> typeof(s) stripl(const(char)[] s);
>
> This signature states that it returns the same type as an argument. I
> propose that that pattern means stripl can accept _any_ subtype of
> const(char)[] and return that exact type. Inside the function, however,
> the type of s is the type declared, thus restricting its use.
>
> I need to convince myself that function bodies of this type can be
> reliably typechecked, but first I wanted to run it by everyone to get a
> feel of it.
>
> Equivariant functions are not (necessarily) templates and can be used as
> virtual functions. Only one body is generated for one equivariant
> function, unless other template mechanisms are in vigor.
>
> Here are some examples:
>
> a) Simple equivariance
>
> typeof(s) stripl(const(char)[] s);
>
> b) Parameterized equivariance
>
> typeof(s) stripl(S)(S s) if (isSomeString!S);
>
> c) Equivariance of field:
>
> typeof(s.ptr) getpointer(const(char)[] s);
>
> d) Equivariance inside a class/struct declaration:
>
> class S
> {
> typeof(this) clone();
> typeof(this.field) getfield();
> int field;
> }
>
> What do you think? I'm almost afraid to post this.
>
>
> Andrei
Now that I thought about it a little more (please, see and comment my post
about typeof(this) nearby), I agree that the issues are related.
However, the best solution could be a combination of both.
For example, I agree that interface IClonable should be as follows:
interface IClonable { typeof(this) clone() const; }
But how about this scenario:
class Bar : IClonable {
this(const(Bar) proto) { ... }
typeof(this) clone() const { return new Bar(this); }
}
const(Bar) bar = ...;
auto barClone = bar.clone(); // typeof(barClone) is const(Bar)!
Knowing you, I expect you will suggest to change the IClonable interface
as follows:
interface IClonable { DropConst!(typeof(this)) clone() const; }
Although I think that templates would be very useful here, I think in many
cases they make things more complex than it could be. I believe things
could get better if we would decouple the issue into two orthogonal
concepts:
a) typeof(this) is a type of 'foo' in a foo.bar() expression without any
qualifiers applied.
b) 'same' gives the qualifier which is common across all arguments 'same'
is applied to (for example, void foo(same(A) a, same(B) b, same(C) c);,
same is none, const or invariant).
Besides, I would be great if typeof(this) would be interchangeble with the
actual type of this with little or no loss of meaning.
For example, with typeof(this) returning class type with all the
qualifiers and no 'same', we would write:
class Foo
{
typeof(this._bar) bar() const { return _bar; }
private Bar _bar;
}
This have two negative sides:
1) we expose our internal structure
2) we need some template trickery if there is no member of type Bar inside
Foo:
interface Foo {
PassQual!(typeof(this), Bar) bar();
}
3) the typeof(this._bar) is not interchangeble with Bar (qualifiers are
lost) and function doesn't work anymore (can't cast const(Bar) to Bar
implicitly)
I also believe it is important to do the trick *without* templates,
because there are people that don't like templates or don't know about
them (e.g. starters).
With 'same' returning qualifiers of the arguments (hidden ones included),
we could write as follows:
interface Foo {
same(Bar) bar() same(this);
}
interface IClonable {
typeof(this) clone(); // nothing else is needed! typeof(this) doesn't
contain qualifiers.
}
note that changing typeof(this) -> IClonable works well here, too.
Another example. We will use both of the concepts in pair now:
class A
{
same(typeof(this)) foo() same(this) {
// do something
return this;
}
}
class B : A
{
same(typeof(this)) foo() same(this) {
super.foo();
bar();
return this;
}
void bar() {}
}
Little is changed if we replace typeof(this) with A and B in first and
second classes respectively.
A short summary:
- typeof(s) is not very suitable for deducing qualifiers. It is quite ugly
and not intuitive. See Christopher's comment:
On Wed, 15 Oct 2008 04:09:59 +0400, Christopher Wright
<dhasenan at gmail.com> wrote:
> typeof(s) foo(const(char)[] s)
>
> This looks like it's shorthand for:
> const(char)[] foo(const(char)[] s)
>
> And the examples with typeof(A), where A is a type, just didn't make any
> sense.
- There are cases where typeof(s) can't be applied at all (e.g. multiple
argument parameters). inout/same is essential here
- typeof(s) requires templates in way too many cases. same could be used
to pass qualifiers without templates
- dividing the concepts into two orthogonal may make it both easier to
implement and easier to understand
- this will also not overload the meaning of typeof() with too much
different concepts
This is just an idea, what do you think?
More information about the Digitalmars-d
mailing list