Jump to page: 1 2 3
Thread overview
DIP66 v1.1 (Multiple) alias this.
Nov 02, 2014
IgorStepanov
Nov 02, 2014
IgorStepanov
Dec 23, 2014
IgorStepanov
Dec 24, 2014
Walter Bright
Dec 24, 2014
Daniel Nielsen
Dec 26, 2014
Daniel N
May 08, 2015
Andrea Fontana
Jun 27, 2016
mogu
Jun 27, 2016
mogu
Dec 26, 2014
Walter Bright
Nov 02, 2014
Sativa
Nov 03, 2014
IgorStepanov
Nov 03, 2014
Marc Schütz
Nov 03, 2014
IgorStepanov
Dec 06, 2014
IgorStepanov
Dec 21, 2014
deadalnix
Dec 21, 2014
Dicebot
November 02, 2014
http://wiki.dlang.org/DIP66

I've applied some changes to it, however there are still some unresolved questions.

> Here's my destruction:
>
> * "symbol can be a field or a get-property (method annotated with @property and taking zero parameters)." -> actually:
>
> (a) the @property annotation is not necessary
> (b) there may be one ore more parameters so long as they're all defaulted
>
> So the text should be "obj.symbol must be a valid expression".

Done.

> * "At the AliasThis declaration semantic stage, the compiler can perform the initial checks and reject the obviously incorrect AliasThis declarations." -> it might be simpler (for the sake of simplifying generic code) to just delay all error checking to the first use.

I disagree with that. Current check is not recursive and prevent you code from a silly errors:

struct X(T, V)
{
   T t;
   V v;
   alias t this;
   alias v this; //Error if is(T == V). However this code is fundamentally broken, and this error should be raised as soon as possible.
}

class A : B
{
   B b;
   alias b this; //Error: super type (B) always hides "aliasthised" type B because base classes should be processed before alias this types.
}

> * I don't think the pseudocode helps a lot. Better let's have a clear and precise specification (I've edited the lookup order into an ordered list).

Done.

> * Regarding the lookup, opDispatch shouldn't come before alias this, or should come before base class lookup. Essentially alias this is subtyping so it should enjoy similar privileges to base classes. A different way to look at it is opDispatch is a "last resort" lookup mechanism, just one step above the UFCS lowering.

I agree with this suggestion, however it breaks an existing code.
opDispatch shouldn't come before base type lookup, because it will hide basic methods like toString.
opDispatch may come after alias this lookup, however it will fundamentally change program behaviour.

Current (implemented is released compiler) behaviour:
import std.stdio;

struct Base
{
    string foo()
    {
        return "Base.foo";
    }
}

struct Derived
{
    Base b;
    alias b this;

    string opDispatch(string s)()
    {
        return "opDispatch";
    }
}

void main()
{
    Derived d;
    writeln(d.foo()); //prints "opDispatch"
}

After your change this call will write "Base.foo". And I see no way to add third "transitional" state to allow users rewrite those code correctly.
This changing will suddenly, without any warning, change a user code. I understand that this case is very rare however it may be.

And, TBH, this issue not relevant with multiple alias this :-)


> * The DIP should specify the working of alias this as rewrites/lowerings, not pseudocode. Basically for each kth declaration "alias symbolk this;" the compiler rewrites "obj.xyz" as "obj.symbolk.xyz" and then does the usual lookup on that expression. That means the whole algorithms is applied again etc. If more than one rewrite typechecks, that's an ambiguity error.

Ok. I've removed pseudocode. Is it better now?

> * IMPORTANT: The DIP must discuss rvalue vs. lvalue cases. The rewrite approach simplifies that discussion because it's clear what happens by simply reasoning about the rewritten expression. Lvalue vs. rvalue matters a lot practically. Consider:
>
> struct A
> {
>     private int x;
>     alias x this;
> }
>
> struct B
> {
>     private int _x;
>     int x() { return x; }
>     alias x this;
> }
>
> Then x can be passed by reference, modified directly etc. for A but not for B.

Done. I've added corresponding chapter to the DIP and commit to the PR.
November 02, 2014
And there is dispute about is expression: see http://forum.dlang.org/thread/ubafmwvxwtolhmnxbrsf@forum.dlang.org?page=5
November 02, 2014
> class A : B
> {
>    B b;
>    alias b this; //Error: super type (B) always hides "aliasthised" type B because base classes should be processed before alias this types.
> }
>

This isn't an error? Just because A inherits B doesn't mean that the alias is wrong?

e.g., If you have

class A1 { B b; alias b this; }

and

class A2 : B { B b; alias b this; }

A2 is just A1 in all forms. Whats great about it is that it allows easy composition patterns. (that is, A1 has essentially implemented B through the alias)

e.g., we can reassign b to a different behavior without having to write any boil code. It allows one to decompose classes and their implementations in a natural way.

Of course, it would require the alias this to be processed before the base class.

I think that it would be worth the alias to override the inheritance because it makes things easier:


class A : BBB { BB b; alias b this; }

If BBB and BB are related in some complex way the compiler has to deduce this. e.g.,

class BBB : Q!B { }

where Q!B is some template that is a superclass of B.

But if the alias is processed first, it won't matter.


>> * Regarding the lookup, opDispatch shouldn't come before alias this, or should come before base class lookup. Essentially alias this is subtyping so it should enjoy similar privileges to base classes. A different way to look at it is opDispatch is a "last resort" lookup mechanism, just one step above the UFCS lowering.
>
> I agree with this suggestion, however it breaks an existing code.
> opDispatch shouldn't come before base type lookup, because it will hide basic methods like toString.
> opDispatch may come after alias this lookup, however it will fundamentally change program behaviour.

Why can't you simply have opDispatch call the base class lookup if all others fail?

It seems to me that one should have

alias this > opDispatch > class > base class(es)

But each one has a fall through mechanism.

e.g., if someone overrides toString in opDispatch it will call their function, if not, it gets passed to the bass class tostring.

Why should it work this way? Because alias this and opDispatch are user defined. The user "knows" why they are doing and the compiler doesn't get in the way by preventing them from doing things they want to do.

The compiler essentially fixes up all the missing connections that it can but never forcing connections the user may not want.


Basically all one needs is something like

bool opAliasDispatch(...)
{
   if (...) { ... return true; } // tries to dispatch
   return false;
}


bool opDispatch(...)
{
   if (...) { ... return true; } // tries to dispatch
   return false;
}

bool opClassDispatch(...)
{
   if (...) { ... return true; } // tries to dispatch
   return false;
}

bool opBaseDispatch(...)
{
   if (...) { ... return true; } // tries to dispatch
   return false;
}

Then a master dispatcher is

bool opMasterDispatch(...)
{
   return opAliasDispatch(...) || opDispatch(...) || opClassDispatch(...) || opBaseDispatch(...);
}



This makes it easier to add new dispatchers in the future, etc.

Also, if you are worried about backwards compatibility, just create a command line switch to select one method over another. Easy and it doesn't force everyone to be stuck with a suboptimal solution just for "backwards compatibility"(which is the scourge of humanity... We have to move forward, not be stuck in the past!!!).

November 03, 2014
On 11/2/14 6:55 AM, IgorStepanov wrote:
> http://wiki.dlang.org/DIP66
>
>> * "At the AliasThis declaration semantic stage, the compiler can
>> perform the initial checks and reject the obviously incorrect
>> AliasThis declarations." -> it might be simpler (for the sake of
>> simplifying generic code) to just delay all error checking to the
>> first use.
>
> I disagree with that. Current check is not recursive and prevent you
> code from a silly errors:
>
> struct X(T, V)
> {
>     T t;
>     V v;
>     alias t this;
>     alias v this; //Error if is(T == V). However this code is
> fundamentally broken, and this error should be raised as soon as possible.
> }

The code is not fundamentally broken if alias this is never used. I agree rejecting the code compulsively is also sensible, ONLY if there is a simple way to write a static if condition to make the code work. Meaning:

struct X(T, V)
{
    T t;
    V v;
    static if (please_fill_this)
        alias t this;
    static if (please_fill_this_too)
        alias v this;
}

If the two conditions are too hard to write then it would be difficult to argue this point successfully.

> class A : B
> {
>     B b;
>     alias b this; //Error: super type (B) always hides "aliasthised"
> type B because base classes should be processed before alias this types.
> }

That's fine to reject in all cases.

>> * Regarding the lookup, opDispatch shouldn't come before alias this,
>> or should come before base class lookup. Essentially alias this is
>> subtyping so it should enjoy similar privileges to base classes. A
>> different way to look at it is opDispatch is a "last resort" lookup
>> mechanism, just one step above the UFCS lowering.
>
> I agree with this suggestion, however it breaks an existing code.

Walter and I would agree to making the presence of BOTH alias this and opDispatch a compile-time error. That would break existing code but not change semantics silently.

When alias this was introduced the decision of opDispatch vs. alias this was not deeply elaborated. In hindsight opDispatch should probably have come after because it's really a "method not found" catch-all whereas alias this is subtyping. That said, compelling arguments might come later the other direction. So making it an error for now would be sensible. It should only affect rather obscure code.

> opDispatch shouldn't come before base type lookup, because it will hide
> basic methods like toString.

Agreed.

> opDispatch may come after alias this lookup, however it will
> fundamentally change program behaviour.

Understood.

> Current (implemented is released compiler) behaviour:
[snip]

Understood. All: okay to make alias this + opDispach applicable to the same expression an error?

> And, TBH, this issue not relevant with multiple alias this :-)

Agreed. It is, however, good to revisit the decision and tighten that screw properly now that we have the opportunity.

>> * The DIP should specify the working of alias this as
>> rewrites/lowerings, not pseudocode. Basically for each kth declaration
>> "alias symbolk this;" the compiler rewrites "obj.xyz" as
>> "obj.symbolk.xyz" and then does the usual lookup on that expression.
>> That means the whole algorithms is applied again etc. If more than one
>> rewrite typechecks, that's an ambiguity error.
>
> Ok. I've removed pseudocode. Is it better now?

I'm flying now :o)... will take a look.

>> * IMPORTANT: The DIP must discuss rvalue vs. lvalue cases.
>
> Done. I've added corresponding chapter to the DIP and commit to the PR.

I'm sending this now with these points, will make one more pass through the DIP when I'm online again.


Andrei

November 03, 2014
>>> * "At the AliasThis declaration semantic stage, the compiler can
>>> perform the initial checks and reject the obviously incorrect
>>> AliasThis declarations." -> it might be simpler (for the sake of
>>> simplifying generic code) to just delay all error checking to the
>>> first use.
>>
>> I disagree with that. Current check is not recursive and prevent you
>> code from a silly errors:
>>
>> struct X(T, V)
>> {
>>    T t;
>>    V v;
>>    alias t this;
>>    alias v this; //Error if is(T == V). However this code is
>> fundamentally broken, and this error should be raised as soon as possible.
>> }
>
> The code is not fundamentally broken if alias this is never used. I agree rejecting the code compulsively is also sensible, ONLY if there is a simple way to write a static if condition to make the code work. Meaning:
>
> struct X(T, V)
> {
>     T t;
>     V v;
>     static if (please_fill_this)
>         alias t this;
>     static if (please_fill_this_too)
>         alias v this;
> }
>
> If the two conditions are too hard to write then it would be difficult to argue this point successfully.

This code can be rewritten as:

struct X(T, V)
{
    T t;
    V v;
    alias t this;
    static if (!is(V == T))
        alias v this;
}

>The code is not fundamentally broken if alias this is never
> used.

I meant that when you say that X is a subtype of T and X is a subtype of V where you don't know what T and V are, it means you don't really know what you're doing. And that is an error and the compiler should inform you about it as soon as possible. However I may be mistaken.

>Understood. All: okay to make alias this + opDispach applicable to the
same expression an error?

I think it will be nice.

>I'm sending this now with these points, will make one more pass through
the DIP when I'm online again.

Ok, I'll wait.

And please, answer the question about the is-expression.
November 03, 2014
On Monday, 3 November 2014 at 15:39:42 UTC, IgorStepanov wrote:
> I meant that when you say that X is a subtype of T and X is a subtype of V where you don't know what T and V are, it means you don't really know what you're doing. And that is an error and the compiler should inform you about it as soon as possible. However I may be mistaken.

IMO the behaviour should be analogous to name lookup for modules: there should be an error only on use. It's hard to come up with a non-artificial example, but I can imagine there are some valid use cases in generic code. It won't hurt to report the ambiguity error on use, while it could theoretically hurt to report it early, so I'd suggest to go with the former.
November 03, 2014
On Monday, 3 November 2014 at 20:06:27 UTC, Marc Schütz wrote:
> On Monday, 3 November 2014 at 15:39:42 UTC, IgorStepanov wrote:
>> I meant that when you say that X is a subtype of T and X is a subtype of V where you don't know what T and V are, it means you don't really know what you're doing. And that is an error and the compiler should inform you about it as soon as possible. However I may be mistaken.
>
> IMO the behaviour should be analogous to name lookup for modules: there should be an error only on use. It's hard to come up with a non-artificial example, but I can imagine there are some valid use cases in generic code. It won't hurt to report the ambiguity error on use, while it could theoretically hurt to report it early, so I'd suggest to go with the former.

There are two cases:
1: when "alias a this" tries to override base class typeof(a)
2: when "alias a this" tries to override "alias b this" where "is(typeof(a) == typeof(b))"

The first check is hard to implement at lookup-time, because base classes are resolved before alias this.
The second check may be easely dropped (anyway alias this conflicts are resolved properly at lookup time).

Do you accept this scheme (remove the second check but still alive the first check)?
December 06, 2014
Bump
December 20, 2014
On 11/2/14 6:57 AM, IgorStepanov wrote:
> And there is dispute about is expression: see
> http://forum.dlang.org/thread/ubafmwvxwtolhmnxbrsf@forum.dlang.org?page=5

OK, time to get this approved.

First, the current DIP doesn't seem to address this:

> Walter and I would agree to making the presence of BOTH alias this
> and opDispatch a compile-time error. That would break existing code
> but not change semantics silently.

Any thoughts on this? Currently opDispatch gets priority over alias this, see lookup step 3 in section "Semantics" of http://wiki.dlang.org/DIP66. That's problematic because it puts opDispatch in _between_ "normal" subtyping via inheritance and alias this, which is supposed to be just as solid as inheritance.

I think the principled solution is to combine steps 2 and 4 into step 2, i.e. alias this is as strong as inheritance. Any ambiguous symbols would be rejected.

The second possibility, less principled but probably practical, would be to swap steps 3 and 4. That way alias this has juuust a teensy bit a lower status than regular inheritance.

The simplest thing (which Walter favors) is to make the presence of both opDispatch and alias this a compile-time error. That would break only a teensy amount of code if any, and would give us time to investigate the best approach when compelling use cases come about. So I suggest we move forward with that for this DIP.

Regarding the is-expression controversy in http://forum.dlang.org/thread/ubafmwvxwtolhmnxbrsf@forum.dlang.org?page=5:

First off, is(S : T) is a subtyping test - is S a non-proper subtype of T, or not? (Non-proper or improper subtyping: S is allowed to be identical to T). "alias this" is a mechanism that introduces subtyping. It follows that subtyping introduced via "alias this" must be detected with is-expressions.

Now, you give an example of subtyping where one or more two objects of the same supertype may be reached through two or more different paths. This is a well-known problem in subtyping (known as diamond hierarchy or repeated inheritance).

In the case of "alias this", different objects of the same type may be reachable (or at least the compiler is unable to tell statically whether the objects are distinct or not). A correct but hamfisted solution would be to sever the subtyping relationship whenever the same type is reachable through multiple paths.

The versatility of "alias this", however, suggests a better solution: if T is indirectly reachable as a supertype of S through more than one path and the subtyping is either tested (by means of an is-expression) or effected (by means of an implicit conversion), the compiler should issue a compile-time error asking the user to define an "alias this" DIRECTLY inside S, which takes precedence over indirect reachability and informs the type system which T of the several reachable ones is needed.

Please let me know of any thoughts. Thanks!


Andrei
December 20, 2014
On 02/11/14 15:55, IgorStepanov via Digitalmars-d wrote:
> http://wiki.dlang.org/DIP66
>
> I've applied some changes to it, however there are still some unresolved questions.

The current DIP doesn't address protection attributes.  I recognize this might be somewhat orthogonal, but it'd be nice to build it into the DIP if possible, just to be explicit about what is expected for how alias this should work.

According to TDPL the following should work:

    struct Foo
    {
        private T internal_;         // member variable is private

        public alias internal_ this; // .. but can be interacted with
                                     // via the public alias
    }

It seems to me an important factor, because it means that classes and structs can use subtyping without revealing the implementation details.  As things are, you wind up having to do something like,

    struct Integer
    {
        private int i_;

        public ref int getInteger() @property
        {
            return i_;
        }

        alias getInteger this;
    }

... which personally I find a bit of an unpleasant violation of the idea of a private implementation.

See also: https://issues.dlang.org/show_bug.cgi?id=10996
« First   ‹ Prev
1 2 3