Confusion around implicit receiver sends and overrides

77 views
Skip to first unread message

Richard Roberts

unread,
Jan 24, 2018, 8:28:55 PM1/24/18
to Newspeak Programming Language
Hi Gilad,

I apologise in advance for this - I'm trying to ask a question without knowing what the question is exactly.

To give some background, I'm looking into object nesting and trying to understand the semantics carefully. As part of that effort, I have arrived at a point of confusion about how an implicit message should be resolved when inheritance and nested classes enter the picture together. 

Again, I'm not sure how to articulate the question (I suppose I am even confused about being confused); and so instead I will give an example and then highlight one minor thing and one major thing that I don't understand.

class Example usingPlatform: platform = Value ()(

 
class A = ()(
   
public bar = (
     
^ 'bar invoked via A'.
   
)
   
   
public class Inner = ()(
     
public foo = (
         
^ bar.
     
)
   
)
 
)


 
class B = (A new Inner) ()()

 
public class C = ()(
   
public bar = (
     
^ 'bar invoked via C'.
   
)

   
public class B2 = B ()()
 
)

 
public main: args = (
    B
new foo println.
    C
new B2 new foo println.
   
^ 0
 
)
)


So, the first thing to note is that we create an instance of B and send #foo to it (see the #main method). Next, note that B inherits from Inner (the class nested into A). And so consequently at runtime: 

1. B inherits from Inner.
2. An instance of Example encloses B.
3. An instance of A encloses Inner.

To give a diagram:

// -> = enclose
// ^ = inherits

      A
-> Inner
           
^
Example -> B
     


Now, in method foo of Inner in A, the resolution of the implicit message send with selector #bar has only part of that picture; just the lexical resolution:

A -> Inner

So the resolution is to "start at lexical class Inner" and then "step out once" (from Inner to A). Here's my minor point of confusion: this resolution-picture is different from the run-time picture. If we followed the resolution without thinking carefully, we would resolve to the instance of Example and not the instance of A (since Example encloses B). It  seems to me that the only way to get from B to A is:

1. step up from B to Inner
2. step out from Inner to A

Since Newspeak always steps up first, I think that the there must be some hidden logic to get the correct lexical class at runtime, and from that lexical class to find the correct enclosing object (SOMns achieves this by storing the lexical class that handles the send into a field of the Implicit Request's AST node). How do you deal with this for your implementation? I'm aware that I might be mixing up compile and run-time semantics here, apologies again. 

Okay. Hopefully, you can see where I'm going for my main point of confusion - can the class C override the bar method and expect that the implicit send for #bar will invoke the override?

After talking with Stefan, he helped me to try this on your Newspeak implementation, and I think in both cases (yours and SOMns) we get the output:

```
bar invoked via A
bar invoked via A
```

And so, I guess the answer is probably "no the override will not be invoked." Can you give confirm if that's the correct output and help me to why C's bar cannot override A's bar in this case? I think it has something to do with the late binding, but I'm tripping over myself trying to give a precise explanation as to why.


Okay, well, that's enough for now. I'd really appreciate help to clarify the above if you have time!

Ryan Macnak

unread,
Jan 24, 2018, 11:21:44 PM1/24/18
to newspeak...@googlegroups.com
Hi Richard,

An implicit receiver send has two phases: first we find which enclosing object is the implicit receiver, then we find which method of the implicit receiver to invoke.

Let's take your second case, sending bar during `C new B2 new foo println`. In the first phase, we have the chain of enclosing mixins

Inner -> A -> Example

with the corresponding chain of enclosing objects

an instance of B2 -> an instance of A -> an instance of Example

Because, A is the inner-most mixin that defines bar, the implicit receiver is the corresponding enclosing object, an instance of A.

In the second phase, we see the A's bar is not private, so we don't immediately invoke it. We start searching the inheritance chain starting at the class of the instance of A, looking for public or protected methods named bar. (In this case, the enclosing object's class happens to be an application of the corresponding enclosing mixin, but it could have been a subclass.) The first (and only) such bar is A's bar, so that method is invoked. C's foo is not invoked because there's no relevant relationship between C and A; the instance of C is not an enclosing object of the application of Inner.

An example where we do see inheritance in the enclosing object may help:

class Example2 = ()(
   class OuterA = ()(
      public class Inner = ()(
         public bar = ( ^foo )
      )
      foo = ( ^'OuterA' )
   )
   class OuterB = OuterA ()(
      foo = ( ^'OuterB' )
   )
   public main = (
     OuterA new Inner new bar out. (* OuterA *)
     OuterB new Inner new bar out. (* OuterB *)
   )
)

It might also help to read through an implementation

Ryan

Gilad Bracha

unread,
Jan 24, 2018, 11:25:55 PM1/24/18
to newspeak...@googlegroups.com
Hi Richard,

First, let me confirm, the results should be as you say - both calls return 'bar invoked via A'.
There are any number of ways to implement the semantics. We've done different things in Squeak and Javascript (and similarly in versions based on Strongtalk, Dart and in the P-soup VM).

When you have an implicit receiver send, you must determine its receiver; once you have the receiver, you just send the message to it. How do you find the appropriate receiver? You search up the lexical scope until you find a scope that defines the selector you're looking for.
If none is found, its an old fashioned self send.  The Squeak implementation keeps a reference to the enclosing mixin in each method. The mixin is the runtime representation of the lexical scope of in which the method was declared. Each mixin points at its enclosing mixin. 

Hopefully this answers your first question

The overall lookup process is then as described in the 2010 ECOOP paper in section 3.3, and in particular the pseudo-code in Figure 6. As you search up the lexical scope, you also zoom in on the desired enclosing object.  The spec also gives an explanation of this process that is less operational - but also harder to follow. The implementation is a bit subtle, but in realistic cases the behavior is always obvious.

As to the second question: you'll note that C doesn't override A (no one does) so there is no earthly reason that the behavior of Inner>>foo should be overridden by C (or anyone else). If you added

public class D = A ()(
  public class B3 = Inner()()

  public bar = (
      ^ 'bar invoked via D'.
 )
)

to Example, and executed

D new B3 new foo

from main:, you would get 'bar invoked via D' because now the superclass of B3 is an Inner class whose enclosing object is an instance of D, and so the implicit receiver of the bar message would be a D. In the example you showed me however, the superclass Inner where foo is found is always a class with an enclosing instance that is an A.

I hope this helps. 

 


Richard Roberts

unread,
Jan 29, 2018, 3:40:51 PM1/29/18
to Newspeak Programming Language
Hi Ryan and Gilad,

From your replies I can see that I was confused about the semantics for obtaining a class based on its declaration; I've gained a much better understanding after reading through the information above, the explanation given in the ECOOP paper, and the implementation that Ryan linked.

So, thank you both for the very helpful replies, it's really appreciated! 


As to the second question: you'll note that C doesn't override A (no one does) so there is no earthly reason that the behavior of Inner>>foo should be overridden by C (or anyone else).

Haha, yes. Now that I look back on my example, I can help but wonder how I ever decided it should be overridden by C! 

Gilad Bracha

unread,
Jan 29, 2018, 3:45:37 PM1/29/18
to newspeak...@googlegroups.com
Great - I see you have been successfully brainwashed!  I down, 7 billion to go ...
Reply all
Reply to author
Forward
0 new messages