[Grace-core] Notes on Grace

James Noble kjx at ecs.vuw.ac.nz
Sun Dec 9 03:21:08 PST 2012


>  I know that James keeps on saying "Pick any 2", but I now believe convinced that we can get all of the features that we need, from delegation. 

well let's see.  Remember the implementation at one time used delegation, and there were reasons why we changed.
I started to collect a list of use cases for inheritance - there should be 10-20 cases I think - and of course we'd need a 
description of any proposed new semantics...


>> 2.  Delegation instead of prototypes
>> 
>> Thus our old semantics would come from writing 
> 
>> def o1 = object {
>>                    delegates to o.clone
>>                   ...}
> 
> That's right, although I might write "delegates others to o.clone" (in the core calculus, not in the surface syntax).  And the current semantics would come from writing 
> 
> def o1 = object {
>                    delegates others to o
>                   ...}

I've no idea what's meant by the "old" and "current" semantics are here - I'm losing track. 

>> Of course for stateless objects, these two give the same effect.  This is likely to be the case for modules interpreted as objects, for example. 
> 
> Right again.  Even more importantly, they give the same effect for a stateless object that represents a sharable collection of methods, which is mostly what people use an abstract class for.  Such a sharable collection of methods also happens to model a trait, so we could (in th higher language levels) provide the trait meta-operations like +, - etc.

Again, I need a lot more description even to imagine how this might work. We already use "+" and "-" requests - are we going Self-like with special meta-requests that can be sent to _any_ object? or are these meta-operations on mirrors, or are traits more like mirrors (in a sense like types) in that + and - mean the trait operations, and you have to somehow "instantiate" them to get an object you can send normal requests to?  

> 
>> 3. Abstract classes and classes
>> 
>> Before talking about classes, one concern that Andrew pointed out to me seems quite serious.  We need to have abstract classes in Grace.  
> 
> An abstract class is a class that can't be instantiated.  So right now there is no way to inherit from it, because of the freshness rule.
> This is a fatal flaw, I think.

not at all. conceivably an "abstract class" could be one that isn't "complete" - think Smalltalk subclassResponsibility.
In Grace as it is today, we can easily "instantiate" things without any requests or methods whatsoever, by just running
the object constructor. Here's an example that could work today: 

method abstractComparableTrait { object {
  method compareTo(other) is abstract {} 
  method < (other) {compareTo(other) < 0}
  method > (other) {compareTo(other) > 0}
  method = (other) {compareTo(other) == 0}
}} 

>  As I pointed out above, if there is no state in the thing being delegated to, the question of whether it is copied or not is irrelevant.  (It's not irrelevant in a "live" programming environment (like Smalltalk's), when there is a possibility of editing the code.  But we are ignoring that issue, I think.

assuming/if/when Grace gets a full writable reflexive layer, then its an issue even without the IDE that would sit on top of it. 

the real catch is "state" or, as Marco puts it, self-capture,  is more subtle than first appears:

method handler(name' : String) -> Handler { 
object {   
  def name = name'
  def callbackBlock = {print "handling callback for {name}"}
}}

is this object OK or does it capture self?   I think not for about three reasons, probably more.
(what's print?  what's name?  if we write name' is that different?  what about the block literal?)

>> Currently we can't define them without putting in some kind of default method body (which returns something of the appropriate type).  We need to be able to label a method as abstract and have that block the execution (but not definition) of creation code.  That is, abstract classes should not be able to be constructed as is, but their creation/initialization code needs to be executed when a (non-abstract) subclass is created.

we have generative object constructors we can run to make things. 

> 
>> Delegation makes no difference there, though strictly speaking the semantics is more complicated.  It seems to require that inherited methods be obtained by tracing pointers to superclass objects, which adds inefficiency (just as Smalltalk inheritance is very expensive compared to Java/C++ inheritance using v-tables).
> 
> This is a misconception, I think, but not really relevant to the current discussion.  Smalltalk chose to share method dictionaries rather than copy them down not because of its semantics but because it wanted to support an interactive environment, and to minimize memory footprint.  The semantics could be supported by copy, and indeed, that's how we implemented traits.  But the cost of lookup is irrelevant when 99% of your method lookups hit in the cache. I f they don't hit, then I suspect that lookup will be too slow however we do it.

it gets worse when you have to chase pointers to different objects.  But VMs are always making great improvements in things. 
With a Grace aware VM, you'd hope it would lay the whole object out in one block anyway - but unlike Java, allow separate
pointers to the nested partial objects, and then generally have the JITTer do the Right Thing.  I don't think any current VMs
do that, but I'm sure Self would optimize through types of parent fields etc.

>> In the absence of solutions to those problems I'd rather just define class inheritance from scratch to include abstract classes as well as concrete classes and to have rules that would bar the construction of objects from abstract classes except as part of the construction of concrete classes.  That is, I want essentially the Java rules for classes/subclasses/abstract classes.
> 
> That means adding two more concepts.  

well yes, but at least everything is well understood, and we said we we're aiming to do research.
Now that was always going to be a white lie, but it seems the interaction of the object model we picked
are rather more subtle than I first hoped they would turn out.

> I see my proposal to add a trait keyword to be much more modest, since it is just a restriction on an existing concept (an object without state), rather than a new one.

hmmm.  Without more details, I'm not sure I can see much difference (other than the spelling of a five-letter keyword). 

I do think multiple inheritance (or whatever it is) would be a step-change in the design. 
Perhaps we should decide whether (or not) we want to do that, and if so, what model 
(traits or mixins seem the top two) we want to pick and work back from there. 

> I was actually pointing out that all th languages that I'm aware of do already make this distinction, but don't do it vary clearly.

O'CAML does I think, with explicit zeroary initialisers that run top-to-bottom super-to-subclass.

> Once we are explicit about the two pauses, I think that it will be entirely reasonable to ban self references in the creation code.  This hypothesis remains to be tested, of course.

even Java has quite a bit of "post-constructor initialisation". 
Whether that's because of sloppy coding; complex construction (builders of various kinds); 
or just an necessity I'm not sure -  but I can ask an ex-student who did a PhD looking at 
construction of Java objects in DaCapo. In general, the more restrictions placed on construction
code, the more weaker the enforced invariants in practice, and the more code gets pushed out of 
the construction (or/and initialisation) phase.

> There is also code in my slides that show how to do the initialization protocol with an "initializable" trait.  

well yes, but again that can't initialize defs - or not without some special other support,
and objects can be doubly initialized. 

> That's what we had.  The problem was that you wanted it to do different things depending on the context in which the object was created.  By putting the initialization code in a method (which is what Java does --- the classname."<init>" method) we get the ability to call it "late", that is, after the sub object has been created.  So you would get the Java semantics, because we would be using essentially the same mechanism.

well right - but no, because a normal method can't initialize defs.
At least our "uninitialized" semantics seem more robust that jamming in nulls and hoping. 

>> I don't think I have that strong feelings about it (though people could convince me to go a different direction if there were compelling arguments or examples for one direction or the other)
> 
> Have I convinced you yet?

me - no - partly because I'm still not sure what you're proposing. 

I'm also getting increasingly unhappy about any solution that depends on cloning - 
again because that means we have *two* generative constructs in the language -
objects constructors and clone. But I don't see how to e.g. do the kind of lexically
scoped stuff we do now with singleton object literals (as in Self).   Writing clone
in some meta-level is possible, but again it may have to be magic to deal with 
def etc.    

But I'm increasingly of the opinion that we don't need to worry too much about not
supporting inheritance from def'ed singletons.  If you write def x = {} well then 
that object cannot be inherited from.  Write the almost equivalent method x {object ,,,} 
and you're fine. 

J


More information about the Grace-core mailing list