[Grace-core] Classes and inheritance

James Noble kjx at ecs.vuw.ac.nz
Wed Jul 13 23:06:18 PDT 2011


> The superclass should be resolved at the same time the expression using the superclass is evaluated.  In particular, an object or subclass should not change meaning dynamically just because during its lifetime the value of the identifier holding the superclass changes.

right.  

> However if C is a variable holding a class then even writing 
>    class A {x -> extends C.new(x) ... method m(..){...} ...}
> can be problematic as we would need some way of determining whether m overrode an existing method of C.  In the statically typed world, this could be done by explicitly writing the type of variable C (a class type that listed everything accessible to subclasses), though that would be a bit of a pain to write down and would likely limit the value of C to be extremely constrained.  In the dynamic world it would require run-time checks when this is evaluated.

C isn't the class type, thought, C is the *factory* :-)

I just think we want to think very carefully how dynamic we want (non-reflexive) class composition to be.

I can see little justification for going further than Smalltalk :-) 

> Having defined (and implemented) a statically-typed language where classes are first class, I must admit that aside from theoretical issues, I found little practical use for class variables, parameters, etc.  In my opinion they are more trouble than they are worth.  

well especially as we have generics...

> As a result, I would be in favor of not having "first class" classes.  Thus I'd allow
> 	class C {...} 
> or equivalently
> 	const C := class {...}
> though I prefer the first ...
> 
> but not
> 	var C := class {...}

right. 
This doesn't mean that people can't make classes dyanmically - 
but that they would be reflexive operations 
(and this, in some sense, optional for implementations to support)

> 
>> 
>> (1b) Related to both of those, there is a typing issue here of some
>> sort: the instances of the class should also be instances of the type
>> of the instances of the superclass, which I think means something
>> complicated for the resolution rules.
> 
> If you mean that the objects generated by the subclass should have a type that is a subtype of those objects generated from the subclass, then that should be easy (at least in the absence of MyType or SelfType in the language), assuming that there are reasonable constraints on overriding methods (no new hiding, subtyping parameters, etc.)

indeed. 

>> 
>> (2) How can you write mutually-recursive classes? Since "class X { }"

> To make this work you generally process (the names of) all declarations added in the current scope before evaluating any of them.  =

right. 

> ML (awkwardly) requires you to join the declarations with an "and".  E.g.:
> I do NOT recommend that.  Haskell, for example, adopts the rule I suggested above.

we need a convention for the REPL I guess - 
but that might also allow redefinition of classes / methods
 for debugging.    Shouldn't be in the language spec, though?

> Now if you evaluate the right hand sides, you are OK as long as the mutual recursive stuff is in closures.  That is, when you evaluate A above, you don't evaluate B.new as it is in a block.  However, you do record where to find B so that when necessary you can evaluate B.new.
> 
> If instead you wrote
> 
> const x = y + 1
> const y = x -1
> 
> then you clearly have a problem as y does not have a value when x is being evaluated.  On the other hand
> const x = {z -> x + y}
> const y = {w -> if (...) {x(w)} else {...}}
> is again fine as the current value of y is not needed in evaluating x.
> 
> (Of course one can still write non-terminating computations, but that is a different issue.)

Another different-but-related issue is that you can access uninitialised or partly initialised objects.
Which should get you an immediate exception at runtime - 
I wonder if a compiler could help here too? 

> As I've stated before, I don't like the concatenation syntax, as it suggests that everything is done via search, which is not done in any reasonably efficient OO language and I believe it is more complex than necessary.  The simpler semantics is that to form a subclass, start with the method suite (semantics, not syntax) for the super class, fill those in the corresponding slots for the new subclass, add the new methods and replace the old methods that are overridden.  (Instance variables are even simpler)  Any references to super should access the methods from the superclass, though with self in the body interpreted as the new object of the subclass.

sure - the trick here being how "references to super should access the methods from the superclass"

> As a result, any reference to super.speak in the subclass B (as in method test) should always execute method speak from A, i.e., print "Hello".
> It is important that you take the values of these methods from the superclass and not just their syntax as textual substitution would really mess you up.

really thought the question is - what semantics for super do we want? 
we picked on super for compatibility with Java, Smalltalk, C# etc. (Python?)
It's probably still the right choice - I can't see us going for "inner".

If we really want the concatenation syntax, I could see us going to "next' - basically "call-next-method" or "proceed"
from CLOS & AspectJ respectively. But, these are more restrictive than super, they don't allow "sideways" super calls.
The last time we talked about it we converged on super: does anyone want to reopen that?

James






More information about the Grace-core mailing list