[Grace-core] Inheritance and Template Objects

James Noble kjx at ecs.vuw.ac.nz
Fri Apr 5 16:17:23 PDT 2013


>> this assumes true has a "template" method which is basically clone or something very close.
> 
> No, I obviously didn't explain this very well.

there are bits I didn't understand - I think you comment below explains things very well.

what I didn't explain well in my previous messages is that when "encoding" templates into classes (which is what my previous message tried to do) is that to encode "inherits true" - in the classist style_ we need a method that is (philosophically) a tail call to an object constructor.
But that still assumes we keep the "handbuilt class encoding" .

Without the handbuilt class encoding, or extracting implicit classes, 
the most straightforward classist encoding just makes "True" a class and "true"  its instance. 

class True.new {
 // usual stuff
}

def true = True.new 

then we can do this

class aMatch.successful(b, m) {
				inherits True.new 

with no problems


>  It's not "clone", it's "dement", as in "suck out my soul".   The template is NOT a boolean, it's a meta object that describes the methods — and in general also the initialization — that make the object work.  It's the "soul" of an object.

Yep. It's basically the "implementation class" of the object. 

> The template is a constant.  So there is no need to generate a new one each time. 

But in the template model, the templates are instantiated whenever they're inherited.
Or used via the naked object form I guess - when writing  "object Template"

> It's an explicit goal of mine not to have unnecessary incantations.  

Sure.  But the question then is whether we need a third concept -
a template - as well as objects and classes that we've already got

> Let me try to explain this one more time. 
> 
> The point of my proposal was to be EXPLICIT about when we are using a template and when we are using an object.  Object construction and inheritance require no object mutation,

only the current "reverse become" semantics require that, and that's what we're hoping to get rid of.

None of the more recent proposals have mutation visible in user-level code.

> and there is no loss of referential transparency: an expression has the same meaning regardless of context.  

right - this is the advantage here. (more or less).

>  The initialization code — retaining which in re-executable form was the whole point of this long digression into alternative inheritance semantics — is explicitly part of the template, and it isn't executed until an object is instantiated from the template. At that time any selfs in the template are bound to the object being instantiated.   This behavior is different from anything else that we have in Grace.   It's not what ordinary objects do, which is why making aCartesianPoint.x(a)y(b) answer an object rather than a template doesn't work.

So you're right, with the factory / tailcall semantics rule, when aCartesianPoint.x(a)y(b) is requested as part of the inherits clause it is interpreted slightly differently.   That's
the non-referential-transparency you abhor.  But overall semantics *are* the semantics
of templates that you describe.   What I probably should have done is written your cartesian point example as a class:

		class aCartesianPoint.x(a)y(b) { 
					def x is public = a
					def y is public = b 
					print "nothing will be executed until an object is made from this template"
			}  

which actually makes it simpler & clearer to me anyway.   When this class is instantiated from within an inherits clause of a class or object constructor, the semantics are the same as your template semantics.

What I'm trying to get to are the essential difference between the proposals, ideally in terms of individual features, against a lowest-common-denominator background. So something like this:

the template proposal:
 - allows any object's class to be extracted (via a x.template request)
 - classes *do not* understand any normal requests, not even their constructor methods
 - classes methods (including their constructor) can only be involved via inherits clauses of class or object constructors
 - we have a version of the "handbuilt class encoding" via the "template" keyword

If I now understand Andrew's proposal correctly, what I think it boils down to is that
class declarations expand slightly differently than before.

instead of the class declaration 

class C.m(mm) {
  body
}

expanding to 

def C = object {
   method m(mm) {return object body}
}

they now would expand to

def C = object {
   method m(mm) {return **template** body}
}

that template does not become an object - the init code does not run - until you
use it in another object constructor or inherit it - writing 

object template  or class C { inherits tempalte  


in comparison, the purest classist proposal I can think of this morning:
- classes must be declared explicitly with "class" 
- classes *do not* understand any normal requests, not even their constructor methods
- classes methods (including their constructor) can only be involved via inherits clauses of class or object constructors
- we have no handbuilt class encoding.

This requires classes to be instantiated by writing  "object class.method(mm)" 
(or "new class.method(mm)"  --- failing to stay away from syntax!)  -- doesn't
allow just writing class.method(mm). 

Most of the time, I've been assuming we would keep this one:
 - classes understand their constructor methods as normal requests

allowing us to write "class.method(mm)"  in normal code.   The version of the classes/factory method proposal I actually had in my head last night, it seems, adds handbuilding back in.
 - methods doing nothing but tailcalling object constructors are implicitly classes 
 - clone is defined as a method that tailcalls an object constructor

then *all* of these proposals have another option:
 - classes understand methods that implement a trait algebra

Thinking about it,  perhaps this is where andrew's proposal works best.  
Because in the classist proposals you'd write something like 

   class Random {
     inherits Stream.new("binary") + AtomicClockSeed("jodrellBank.govt.uk") 

to get traits - this isn't a normal expression, it's a class/trait expression:
instantiate those two traits and do the trait operation + on the result.

But if you start thinking about it as an object expression it is  incoherent -
you're *not* requesting + as a base level method to the stream.   
But in Andrew's proposal, Stream.new() and  AtomicClockSeed return *templates*
("pre-objects") and the + combines those pre-objects.  And then the "inherits"
clause blesses that pre-object, turning it into a new actual object and running 
all the code at that point.   Which makes a lot more sense. 

But it is also more complex, we've got class declarations, 
constructor methods sent to classes that return template objects, 
inherits clauses turning those templates into real objects. 

And so there are more problems that can arise, such as: 
What happens if you write C.new in open code?  I guess you get the template,
not a new object - until you write "object C.new"?

James




More information about the Grace-core mailing list