gracelang / language

Design of the Grace language and its libraries
GNU General Public License v2.0
6 stars 1 forks source link

purely dynamic operational semantics for inheritance #136

Closed kjx closed 6 years ago

kjx commented 6 years ago

I've been thinking about inheritance again. Tim's models in this thesis and the ECOOP papers work OK in a calculus (reducing to a literal object constructor) but I thought I'd like a more practical operational version, something that would capture the minimum necessary restrictions essential in full Grace.

  1. extra argument slot "creatio" in each frame
    • this tracks the dynamic extent of an inheriting chain of object-constructors
    • this is equivalent to an extra (implicit) parameter on every method
    • or (almost) Andrew's compiling to versions of each method
  2. in a "normal request, (e.g in the middle of a method body, or in all of an object-constructor body) creatio set to a sentinel
  3. in a return request (e.g. the argument to a return, or the last request in a method body that will implicitly return), creatio set to the incoming creatio (the implicit argument to the enclosing method)
  4. in a inherit/reuse request (e.g. top level of an inherit or use clause of an object constructor)
    • if there's a incoming creatio, pass that creatio on during the inherit request
    • otherwise, allocate a new "empty" object identity (pop) and pass that identity as the creatio
    • by rule 2. above, all initialisers and inline code in the body of the object constructor passes sentinels as the creatio
  5. when at inherit/use request returns to a call, if the object it returned is not really truly implementation level EQ to creatio - throw a fatal error (not fresh!)
    • could do this on every return but pretty its sure only strictly necessary in returns back into an inherit/use clause.
  6. when an object constructor "runs", the declarations from the constructor (as modified by excludes and alias clauses) are added into the "raw" object denoted by the creatio. initialisers and inline code is delayed - not executed.
  7. when the lowest (outermost) object constructor nominally completes, the delayed code and initialisers are executed depth first post-order.

Note that:

apblack commented 6 years ago

Yes, you basically have the right idea, and this is basically what minigrace currently does. The differences are:

But this has nothing at all to do with the issue at hand, which is whether we should or should not require the shape of the object produced by a constructor to be statically known. At least, I thought that was the issue at hand ... it seems to be getting lost in implementation concerns, which are not even in scope for this issue tracker! If we discard that restriction, we have "Delft Grace" — the language defined by Eelco and Vlad in their SPLASH paper. This language requires that name resolution be deferred until runtime, because it's not until runtime that one knows what methods are in self. For example, the binding of if(_)then(_)else(_) is now dynamic. Of course, as Ungar demonstrated with the Self language, it is possible to get reasonable performance for such a language by inventing the techniques of JIT compilation ... I didn't think that we were in that business. Note also that our semantics is inherently trickier to implement than NewSpeak's — we have to find the set of all resolutions, and complain if it isn't a singleton set, whereas NewSpeak needs to find just the first (in some ordering).

kjx commented 6 years ago

Yes, you basically have the right idea, and this is basically what minigrace currently does.

Apparently this is what Kernan used to do, before alias & exclude went in. I take it you mean "this is minigrace's intended semantics" because this description is rather different from how I understand minigrace implements things.

You have forgotten about alias and exclude

I haven't forgotten about them - I just left them out - along with multiple inheritance / traits, default methods, overriding, structure errors, and no doubt some other stuff as well. I may try to write out do a version somehow including the lot --- Tim's semantics didn't, I don't know about the SPOOFAX version (although I should also read up on that too).

This is also making me realise again that we have quite a complicated design, with lots of twisty little corners.

kjx commented 6 years ago

But this has nothing at all to do with the issue at hand, which is whether we should or should not require the shape of the object produced by a constructor to be statically known. At least, I thought that was the issue at hand

I think it's one of the issues at hand. (Perhaps I'm broadening my crusade.) For example, if we could only come up good description of the semantics incorporating the statically known restriction, then that would mean the restriction was fundamental and we didn't have a choice. My description above omits a bunch of stuff, including the manifest restriction --- but adding that restriction into this description of the semantics would just make the semantics appear more complex to me, not less.

If we discard that restriction, we have "Delft Grace" — the language defined by Eelco and Vlad in their SPLASH paper. This language requires that name resolution be deferred until runtime, because it's not until runtime that one knows what methods are in self. For example, the binding of if()then()else(_) is now dynamic.

I don't see why: you always know at compile time what is in your lexical scope. Without a manifest superclass, you can't catch a bunch of errors with self requests - but without static types, you can't catch a bunch of errors with receiver requests. Either way, the operational semantics are clear, and a dialect checker could enforce manifest requirements and then do the checks if it wanted.

... it seems to be getting lost in implementation concerns, which are not even in scope for this issue tracker!

I'm trying to stay away from implementation concerns --- although I think implementation assumptions lurk more closely behind our decisions than I often realise.

Of course, as Ungar demonstrated with the Self language, it is possible to get reasonable performance for such a language by inventing the techniques of JIT compilation ... I didn't think that we were in that business.

Moth is explicitly in that business - that's the point of running on top of Truffle. Now that your minigrace no longer supports the C backend, the only implementations we have or that are on the horizon are also in that business: minigrace and hopper (such as it is) on JS, Kernan on CLR or JS, potentially another try at RPython if I can find another gullible student. So yes there is an architectural question here, but in Grace, that goes to the architecture of the specification as well as the implementation: how much is in the language core vs how much is implemented in dialects & checkers.