Which activation should initialisation expressions of an inherited object literal be resolved in?

43 views
Skip to first unread message

Richard Roberts

unread,
Nov 29, 2017, 3:20:00 PM11/29/17
to Newspeak Programming Language
First off, please forgive me if my title doesn't make sense - my vocabulary for asking my question is currently a bit limited! 

I've found a surprising error in SOMns's implementation of object literals, which arises when an object literal inherits from the class of another object literal that is declared in a different scope. In particular, the error with the current SOMns implementation is that the initialization expressions of the inherited object literal's class resolve:

- in the activation enclosing the inheritor (the subclass so to speak), instead of
- resolving in the activation enclosing the declaration of the inheritee (the superclass so to speak). 

I'm hoping that someone here might be able to help me understand what the expected behaviour is for Newspeak - if we can get that far I'd be happy to attempt fixing the SOMns error.


Let me start by giving a little bit of context on how I found the error. I'm currently using SOMns to interpret programs written in Grace. To do this, I translate Grace AST into the SOMns equivalent. In particular, I've modeled Grace's objects using SOMns's object literals (this works fine), but I found the error when trying to model Grace's inheritance semantics. Let me start with a simplified example of the Grace program I was using when I found the error:

```
1. class A (name') {
2.    def name = name'
3. }
4.
5. def x = object {
6.    var obj := inherit A('Alice')
7.    print(obj.name)
8. }
```

In Grace, we have an equality rule that says that classes are equivalent to methods that tail return an object, so the class in the program above is syntactic sugar for:

```
  1. method A (name') {
  2.     return object {
  3.         def name = name'
  4.     }
  5. }
  6.
  7. def x = object {
  8.     var obj := inherit A('Alice')
  9.     print(obj.name)
10. }
```

Note that the initialization expression of the inherited object (see line 3) reads an argument belonging to the activation in which the inherited object is declared in. With line 3 noted, let me present the Newspeak program that would result from my AST translator (note that the `objL Superclass (...)(...)` is the syntax SOMns uses for object literals).


```
 1. class Example usingPlatform: platform = Value (
 2.     | private ClassMirror = platform mirrors ClassMirror. |
 3. )(
 4.
 5.    public A: namePrime = (
 6.        ^ objL (| public name = namePrime. |)()
 7.    )
 8.
 9.    public getSuper = (
11.        | obj |
12.        obj:: A: 'Alice'.
13.        ^ (ClassMirror reflecting: obj) classObject
14.    )
15.
16.    public main: args = (
17.        | obj |
18.        obj:: objL getSuper ()().
19.        (obj name) println.
20.        ^ 0
21.    )
22. )
```

So, as you can see to get the superclass for our object (line 18), I first invoke the method `A:` to get the inherited object (line 12) and then reflect on to return its class (lines 13). Again, be careful to note that the superclass contains an initialization expression that refers to the activation enclosing the inherited object's declaration (line 6). 

The surprising error is that our object (line 18) ends up with the slot `name` set to `args` (as in, the array of arguments given to the main method)!  Why: the `name =` expression says to set name to the first argument of the enclosing activation, and unfortunately the enclosing activation when SOMns invokes the constructor is `main`. Consequently, `name` is set to args. Stefan and I have both verified this to be true, unfortunately. 

Intuitively, I would guess that the `name =` expression somehow eagerly evaluated to `namePrime` and somehow store that information in the class. However, doing so would require adding state (about the object) to the class, which seems like a bad idea. So, finally my question - what behaviour should I expect from the Newspeak program above?

One more apology for being a bit long-winded, I hope the question I'm asking makes sense - looking forward to any further discussion!

Gilad Bracha

unread,
Nov 29, 2017, 4:53:56 PM11/29/17
to newspeak...@googlegroups.com
Hi Richard,

Caveat 1 : object literals are not implemented in Newspeak, and if they were, the syntax would differ from yours, which in turn means that the code in Example is not Newspeak code.  Having said that, object literals are specified and I can see what you're driving at.

Caveat 2: I'm answering off the top of my head. I haven't the time right now to check the spec and verify that it says what I believe it should say. I'll check the spec this evening and get back to you with any further thoughts.

With that out of the way, here is what I believe to be the correct answer:

Of course, the desired answer is what you expect, with name being 'Alice'. Let's call the class returned by getSuperclass S. S was created inside the activation of A with argument 'Alice'. Hence class S must be tied to that activation, just like a closure would be. The assignment of name happens in the factory of S. This factory must reference the enclosing closure - the activation of A.


Stefan Marr

unread,
Nov 29, 2017, 7:07:08 PM11/29/17
to newspeak...@googlegroups.com
Hi:

> On 29 Nov 2017, at 21:53, Gilad Bracha <gbr...@gmail.com> wrote:
>
> Caveat 1 : object literals are not implemented in Newspeak, and if they were, the syntax would differ from yours, which in turn means that the code in Example is not Newspeak code. Having said that, object literals are specified and I can see what you’re driving at.

Just for the archives.

I think, the following would be spec-compliant code:

class Example = ()(

public A: namePrime = (
^ (| public name = namePrime. |)()
)

public getSuper = (
| obj |
obj:: A: 'Alice'.
^ obj class
)

public main: args = (
| obj |
obj:: getSuper ()().
obj name println.
^ 0
)
)

> Of course, the desired answer is what you expect, with name being 'Alice'. Let's call the class returned by getSuperclass S. S was created inside the activation of A with argument 'Alice'. Hence class S must be tied to that activation, just like a closure would be. The assignment of name happens in the factory of S. This factory must reference the enclosing closure - the activation of A.

Richard, I am guess, not checking, but could the reason be that we haven’t properly unified the context chain yet?
There are still places where we maintain multiple things instead of one single context object.

Another possibility could be that we don’t treat method activations as possible contexts, but immediately go to the outer object.

If the example is changed to (or the SOMns equivalent):

public A: namePrime = (
^ [ (| public name = namePrime. |)() ] value
)

It still breaks in SOMns, with an unhandled exception.

I fear, the object literal support still got some kinks that need to be straightened out :)

Best regards
Stefan


--
Stefan Marr
School of Computing, University of Kent
http://stefan-marr.de/research/


Gilad Bracha

unread,
Nov 29, 2017, 7:31:20 PM11/29/17
to newspeak...@googlegroups.com
FWIW,  the spec conceptualizes this as chains of enclosing objects, be they activations or instances.  Consider the following rewrite:

public A: namePrime = (
  ^ (_A new: [namePrime]) Nested new
)

class _A new: np (
  | namePrime = np |
) (
   class Nested (| public name = namePrime value. |)()
)

This is almost a legitimate rewrite, though not very efficient. As long as one cannot detect the difference between the activation of A , which should be the enclosing object of the Nested instannce, and the instance of _A that actually is the enclosing object of the Nested instance. Reflection can expose that though. In any event, it's better to use the context objects directly.


Richard Roberts

unread,
Nov 30, 2017, 7:31:44 PM11/30/17
to Newspeak Programming Language
Hi Gilad and Stefan, and 

Of course, the desired answer is what you expect, with name being 'Alice'.

Okay well, at least I haven't gone mad in that case :)
 

Gilad: 
Let's call the class returned by getSuperclass S. 
S was created inside the activation of A with argument 'Alice'. 
Hence class S must be tied to that activation, just like a closure would be.

Stefan:
Another possibility could be that we don’t treat method activations as possible contexts, but immediately go to the outer object. 

Okay, I think I can state the problem precisely, provided that I understand SOMns correctly (less than 42% chance). SOMns builds an anonymous method on the object enclosing the declaration of the object literal. That method, called the superclassResolutionBulder in the VM, is responsible for invoking the inheritance expression. The problem is that the superclass resolution method is not "tied to" the activation in which our object literal was declared. One possible fix might be to change the implementation of the superclass resolution so that it is a closure literal instead of a method on the enclosing object. 

I don't believe this to be valid Newspeak code, but I guess one way to illustrate the idea describe would be as follows:

class A with: np = ( | name:: np | ) ()


public main: args = (
 
| obj |

  obj
= [ ClassMirror reflecting: (A with: 'Alice') classObject] value ()()
)


I believe that this would preserve the chain of activations; what do you think?


This is almost a legitimate rewrite, though not very efficient. As long as one cannot detect the difference between the activation of A , which should be the enclosing object of the Nested instannce, and the instance of _A that actually is the enclosing object of the Nested instance. Reflection can expose that though. In any event, it's better to use the context objects directly.

I'm hoping to use the SOMns reflection as a backend for Grace's reflection library and so I might as well try using the context objects directly as you suggest. If it doesn't work though, your rewrite would be a simple change that would work well enough for our purposes!


Thanks again Gilad and Stefan!

Gilad Bracha

unread,
Nov 30, 2017, 10:14:39 PM11/30/17
to newspeak...@googlegroups.com
On Thu, Nov 30, 2017 at 4:31 PM Richard Roberts <ryka...@gmail.com> wrote:
Hi Gilad and Stefan, and 

Of course, the desired answer is what you expect, with name being 'Alice'.

Okay well, at least I haven't gone mad in that case :)
 

Gilad: 
Let's call the class returned by getSuperclass S. 
S was created inside the activation of A with argument 'Alice'. 
Hence class S must be tied to that activation, just like a closure would be.

Stefan:
Another possibility could be that we don’t treat method activations as possible contexts, but immediately go to the outer object. 

Okay, I think I can state the problem precisely, provided that I understand SOMns correctly (less than 42% chance). SOMns builds an anonymous method on the object enclosing the declaration of the object literal. That method, called the superclassResolutionBulder in the VM, is responsible for invoking the inheritance expression. The problem is that the superclass resolution method is not "tied to" the activation in which our object literal was declared. One possible fix might be to change the implementation of the superclass resolution so that it is a closure literal instead of a method on the enclosing object. 

I don't believe this to be valid Newspeak code, but I guess one way to illustrate the idea describe would be as follows:

class A with: np = ( | name:: np | ) ()


public main: args = (
 
| obj |

  obj
= [ ClassMirror reflecting: (A with: 'Alice') classObject] value ()()
)


I believe that this would preserve the chain of activations; what do you think?

That looks fine (modulo syntax). As Stefan wrote,  your original code this should work as if you had defined A: as

public A: namePrime = (
       ^ [ (| public name = namePrime. |)() ] value
   )

but he says that doesn't work correctly either.


so that the binding to value of namePrime is preserved.

Stefan Marr

unread,
Dec 1, 2017, 2:55:39 AM12/1/17
to newspeak...@googlegroups.com
Hi Richard:

> On 1 Dec 2017, at 00:31, Richard Roberts <ryka...@gmail.com> wrote:
>
> SOMns builds an anonymous method on the object enclosing the declaration of the object literal. That method, called the superclassResolutionBulder in the VM, is responsible for invoking the inheritance expression. The problem is that the superclass resolution method is not "tied to" the activation in which our object literal was declared. One possible fix might be to change the implementation of the superclass resolution so that it is a closure literal instead of a method on the enclosing object.

Yes, for object literals, that’s probably the way to go. And that is likely also going to fix the other issue I saw.

Could you convert these examples into tests, and document all this as an issue on GitHub?

Thanks

Richard Roberts

unread,
Dec 2, 2017, 8:16:19 PM12/2/17
to Newspeak Programming Language
Great, I understand.

Thanks for the help Gilad and Stefan!

Could you convert these examples into tests, and document all this as an issue on GitHub? 

Yes, I'll do this as my first task on Monday morning :)

Stefan Marr

unread,
Dec 5, 2017, 7:17:49 PM12/5/17
to newspeak...@googlegroups.com
Hi:

As a variation on the theme, the following came up as an issue and a question of how the spec is supposed to be read.

Let’s take the following example:

```
class A with: nn = (|public name = nn.|)()

public test = (
| n = ‘Bob'. |
^ A with: n () ()
)

test name println.
test class new name println. (* assuming #class and #println exist *)
```

The question is whether the superclass clause is supposed to capture the lexical scope in addition to the formal parameters of the primary factory signature.

The spec is saying this:

"The formal parameters named in the primary factory
signature are in scope in the superclass clause.”

Not sure how to read the rest.

The example above might be read as capturing `n`, so that all objects of the class of the object literal are instantiated with the name Bob.
That would mean that the superclass clause also captures its lexical scope in addition to the formal parameters.

I wanted to ask you guys whether that’s the expected behavior.
Currently, SOMns doesn’t support it, and I am not yet sure how to add it without bigger changes.

Gilad Bracha

unread,
Dec 5, 2017, 8:41:31 PM12/5/17
to newspeak...@googlegroups.com
The test code should print:
Bob
A does not understand 'new' (* or some message to that effect *)
and that's assuming #class is public (which it used to be, but isn't anymore). If class is not public, you won't even get that far and you'll get

A does not understand 'class'. I'll assume class is public for now so we don't confuse the issue.

Now if A did have a class method  #new that produced an instance of A, it would have to call #with:  and the argument to #with: would set the value of #name. It is not bound to 'Bob'. The text in the spec is saying that you can use the formal parameters of the primary factory inside the superclass clause. So you could write

class A with: n = B with: n (...)(...)

thereby passing factory arguments up the class hierarchy to the factory of the superclass.  I'm not quite sure how you were reading it, but I note that the example doesn't even have a superclass clause.  It certainly doesn't imply any bizarre capture at a distance. Things are lexically scoped in a straightforward fashion.

Hope this helps.


Reply all
Reply to author
Forward
0 new messages