[Grace-core] Considering Traits as Objects in Grace

James Noble kjx at ecs.vuw.ac.nz
Wed Mar 6 14:33:21 PST 2013


Andrew wrote:

> It's clear that I shouldn't have released this draft
> yet, because it's
> obviously confusing, but at least it gives me a list of things to explain
> better in the next revision.


I think it's good this is out now - we need to talk about inheritance, don't we?

Or rather, continue the discussion -- there's a long thread with the name 
"Inheritance and object initialisation" from July 2012 in the mailing list archive...

that's where I said:

> PICK TWO:
> 1. subClass.new prints "world"
> 2. we have object inheritance
> 3. we have the simple explanation of classes in terms of objects (or vice versa)

Where I guess traits trade off is 1 & 3 - 
and the explanation now includes the initializable trait and initialise methods...

it's also not clear to me that traits address things like block localisation;
at least, blocks etc will need to move into the initialize method rather 
than sitting in the constructor --- i.e. programmers "design for subclassing":
an object that isn't planned to be inherited from can be different to one 
(and simpler than) one that is.    So that also pushes on point #2 ---
we have object inheritance but only if you prepare the object to be 
inherited from.   That's getting quite close to saying you can only
inherit from classes...

There are reasons why O'CAML doesn't put straightline code in objects or classes,
but has special-case "initializers" that run *after* the object is completed. 


Here are my other thought on the draft:

* P5 top of section 3.2 Inheritance VII

> The language restricts the superobject to being “definitively static”, which means that the compiler must be able to figure out what methods it has; this is intended to simplify type-checking and to enable checking of the override annotation.


yes but it's more than this. It's certainly to ease a potential optimization for a static compiler e.g to the JVM.
On reflection (pun intended :-)  I think I have a stronger concept here, that (modulo reflection)  objects
should be "metastatic" or "structurally static" -- that the shape or structure of an object, the bodies of its
fields, the objects in its defs **does not change during the course of the program**.   Again this is a 
distinction that is in Self and Smalltalk and many other languages: it is also one of the ways
subsequent languages differ from Self and Smalltalk.   In Self and Smalltalk, an object or classes
shape is fixed when it is defined, it may be changed later but that's explicitly a reflexive operation
Whereas in JS objects, and Ruby (and python?) classes (?) really are dynamically
open, and any time you can insert new methods (and even fields) into an object. 

which side are we on?

If we believe the "structurally static" principle, then it seems that "reverse mutation" semantics, such as they are,
would clearly breach that principle.  My (admittedly weak) defense is that object's structure (and defs etc)
do not change _once the objects are initialized_ --- but that we can observe both def values & even method
suites changing _during initialisation_. It's not ideal, but it's not a show-stopper either. 

Talking to Marco, he suggested a more radical solution (based on his placeholders):  *an object 
(and therefore its methods etc that are part of the structure) cannot be observed until the object
is fully initialized.  In other words no requests on self in the middle of object constructors, nothing
works until the "bottom-most" constructor/class is done, then the object comes into being fully-formed.
One version of this lets people pass around the "self" but not request anything on it, a stricter version
would not even permit that (so registration would not be possible during construction/initialisation,
although binding self via exported blocks would still be) 

This is much more restrictive than most other languages: but the semantics are clear.  
If we can switch "vtbl pointers" it shouldn't be too expensive to implement.
And see Marco's ECOOP paper for a static type system...  

The catch with tightening up the semantics is that people move from Defs to Vars (hopefully vars of an option
type that is initialised as "uninitialised" :-)   

* P9 top of section 5 "Inheriting Initialization" 

> The key observation that simplifies many of the thorny issues around object initialization is that Object initialization is not the same as object creation.

except that with Grace's object constructors -- (aka Emerald object,s O'CAML immediate objects) 
_initialization is object creation_.  Isn't that the whole point of the object constructors?

* passing parameters between "new" and "create"
    objects are created by calling new which calls created - but these don't take parameters
    They could take parameters by making both of 'em variadic --- presumably type Any!
    But that seems really horrible, and I stated fantasizing about hyper-vargs support 
      more or less reflexive (but somehow statically typed) that would generalize my
      wildest generalisation of Grace methods, so that new could accept a bunch of 
      extra keywords and parameters, and would pass those on to create... 

Other things: 

* Writing the rules for freshness & definitively static is still sitting on my todo list.   
Certainly there should be some way to declare / cast freshness away - so a class
that registers objects could still have them treated as fresh. And yes, I'm aware that's 
a loophole through which a truck can be driven

*1 do we believe in immutable objects -- objects with just (per-instance) defs?
*2 do we believe in encapsulation -- are "delegating vampires" Wrong and Evil? 
*3 do we want "confidential" / "protected" semantics.

point *1 means we need a way to initialize per-instance defs: making 'em vars
     and setting them after construction doesn't give us the guarantees that we want.

points *2 and *3 seem incommensurable, on the face of it: a vampire can always get
     access to anything protected.  Solutions are e.g. to only have private, or to use 
     lexical scope (and not even have private?)

========

How to progress: 

I fear we may have to go back to first principles:

 * define what we want to get our of inheritance, what features we want
     - immutable objects
     - protection
     - multiple inheritance, with mixins or linearisation or traits
     - abstract classes
     - immediate objects
     - inheriting only from traits / abstract classes  
     - inheriting / delegating to existing objects
          (there seems to be a weird conflict with e.g. having a class facility where objects can be 
           defined by combining stateless traits, declaring all state in a terminal (left) definition
           that cannot be further extended --- versus a delegation facility that can extend anything!)
         - delegation per send (AmbientTalk, or Self's delegated perform)
         - delegation per object (Self, "def is parent") 
         - dynamic delegation per object (Self, "var is parent" )
  * think about a bunch of scenarios / use cases / patterns that we want, exhibiting those features
  * write code under various designs to exhibit those scenarios.

 * news - I found a list of these patterns in my notebook from 2012. Here's the list! 
to see how gnarly this can all get, see e.g. here: http://www.scala-lang.org/sid/4

James



More information about the Grace-core mailing list