[Grace-core] Inheritance and object initialisation

Kim Bruce kim at cs.pomona.edu
Thu Jul 19 14:14:12 PDT 2012


It's very clear that these issues with inheritance are hard problems and that we need a principled way of deciding the answers.  Here are some principles that I would like to see us adopt.

1.  As much as possible, constructs should have unambiguous semantics.  If not, we should try to change syntax to make it unambiguous, and if that fails, we should try for a simple consistent semantics that is easy to explain.

2.  The semantics of constructs should avoid special cases as much as possible.  The special cases should generally only be necessary to match the programmer's intuition or solve otherwise hard problems.  We will have special cases for the meaning of self and return in blocks, for example, because that conforms to users' intuitions (they don't really see blocks as objects) and it makes it easier for them to write code that behaves the way they want it.

3.  Because we want novices to move from Grace to other industrial-strength languages (or at least languages more capable for programming in the large), we should as much as possible have similar semantics for similar constructs in other languages.  We will not follow that in every case (e.g., for us objects are generative, while they are not in Scala), but we will avoid conflicts where it doesn't have a major impact on Grace.

4.  Our principles for semantic meaning should be clear enough that we can write down general semantics that will provide answers to all of the weird corner cases.

5.  If, after following all of the other principles above, there are still choices for semantic meaning, we should strive to choose the meaning that will be the most helpful to novices in getting their programs to run correctly.  (Very vague, I know, but expressiveness is important!)

-------------------
Now the puzzles that Michael sent out (Thank you Michael!!) have made it clear that the semantics of inheritance is not yet clearly nailed down.  Andrew, James, and I have each sent out answers that differ from each other, and I suspect that none of our individual answers are completely consistent (though I may be wrong).  We clearly need to sort this out soon (though I suspect we won't completely solve it today).

Before I get going on my analysis, I'd like to posit one further point that I hope everyone will agree with.  The following two pieces of code should give the same answer:

def obj = object {def x = 7 ...other stuff...}

class ObjClass.new {def x = 7 ...other stuff....}  // stuff in brackets identical to above
def obj = ObjClass.new

Similarly the following should be equivalent where we pull out a parameter:

class ObjClass.new(b) {def x = b ...other stuff....}  // b a new identifier, all else same
def obj = ObjClass.new(7)

I think it will be common for programmers to think they only need one object with particular characteristics and then decide they need more.  Moreover, that seems to be a good agile code development strategy.  Try something out with a single object and then generalize to a class (generally with parameters for those things that will differ).

By the way, the way I interpret Andrew's semantics (perhaps incorrectly, let me know) is that constructor code in Grace is dispatched statically.  That is, if there is an invocation of method self.m in executable code in a class/object definition then it will always execute the method available in that class/object (even if inherited), but will never execute overriding code from a subclass/subobject.  On the other hand, code inside methods will always be dispatched dynamically.  (Note that this gets even more complicated as constructor code can call a method which calls another method ...).

My favored semantics (at least at the moment) would not make that distinction.  Instead it would treat all code in the {...} for the class or object as executable code for the constructor, and would all be dispatched dynamically.  That is, first space would be allocated for the instance variables, then all the code (including that initializing defs and variables) would be executed.  (I'll leave the order of execution vague, for now).  In a subclass, all of the code of the superclass constructor and the subclass constructor would be executed.  To be more precise:  Take the code from the superclass, replace all items that have been overridden in the subclass by their new definitions, add the new items from the subclass, and then execute (as we normally do with classes).

I've found myself going back and forth when reading Andrew's explanations for his answers and then seeing how Java handled similar situations.  However, I found myself really struck by one of Andrew's last examples.  Here is a simpler version of it, 

class C.new(a) {
     var x:=a
     ...
     method asString {"C object with {x}"}
     print(asString)
}

Pretty trivial, though there may be other defs, variables, and methods.  Now the subclass:

var D.new(a) {
   extends C.new(a)
   ...
   method asString is overridden {"D object with x ={x}"}
}

When we write C.new(7), the system should clearly print "C object with 7".  What happens when we write D.new(12)?  There are several answers possible.
(1)  Nothing at all is printed.  I believe this is consistent with Andrew's answer, though it is a bit weird in this case because we might never have created an object from C.new.  ???

(2)   Prints "C object with 7".  This is also similar to Andrew's semantics because the call of self.asString in the constructor code is dispatched statically.

(3)  Prints "D object with 7".  This would be my answer, as in my (new) semantics, the new definition of asString would replace the old and then all the code in C (with asString replaced with the new method body) -- and any new in D -- would be executed.

While this is a pretty trivial example (as was Andrew's of changing the color of a border in a subobject/subclass), I think it reflects common behavior by students when trying to debug a program.  It also reached out and bit me when I was writing my graphics library.  I had a bit of initialization code that needed to be run in both the superclass and subclass.  But if I put it in the superclass, the code did not do what I wanted because it used the wrong version of self.  Instead, I had to remove the code from the superclass, and directly insert it to be run in all of the subclasses.  (I basically had to copy "canvas.doSetup(self)" into every one of my applications, because Grace didn't bind self to the new object's self, but, when it occurred in the superclass, instead bound it statically -- as Andrew has urged).

[Sorry for not getting this out earlier.  I hoped to have time to revise and extend, but a two hour lunch killed that ...  Perhaps we can walk through this during our meeting.]

Kim



On Jul 19, 2012, at 2:50 AM, James Noble wrote:

> On 18/07/2012, at 12:32 PM, Andrew Black wrote:
> 
>> This has become very long; we should move it to the Wiki.
> 
> well yes, but I've just got to this now, before the telecon tomorrow
> (where I fear we may well discuss this).  So here are my answers:
> note that I *haven't* looked at anyone else's answers in any depth!
> 
> I've listed Michael's old examples, Michael's new examples,
> and then Andrew's examples at the end 
> 
> That way, when my answers are inconsistent or just plain wrong,
> I'll claim it's the first week of term and leave it at that!
> 
>> def a = object {
>>   def x = 1
>> }
>> def b = object {
>>   inherits a
>>   def y = 2
>> }
>> That is required by the "inherits defaultObject" part of the recent
>> notes as well, so I assume it works.
> 
> seems fine to me!
> 
>> Building on the above, I should also be able to write this:
>> def a = object {
>>   def x = object { var t := 2 }
>> }
>> def b = object {
>>   inherits a
>> }
>> Does a.x == b.x?
> 
> yes
> 
>> def a = object {
>>   def x = { self }
>> }
>> def b = object {
>>   inherits a
>>   def y = 2
>> }
>> Does a.x == b.x?
> 
> no
> 
>> Does a.x.apply == b.x.apply?
> 
> no
> 
>> How about this?
>> var tmp := 1
>> def a = object {
>>   def x = tmp
>> }
>> tmp := 2
>> def b = object {
>>   inherits a
>> }
>> Does a.x == b.x?
> 
> yes
> 
>> What is b.x?
> 
> 1
> 
>> var tmp := 1
>> def a = object {
>>   def x = tmp * 2
>>   tmp := tmp + 1
>> }
>> def b = object {
>>   inherits a
>> }
>> Does a.x == b.x?
> 
> yes 
> 
>> What is b.x? 
> 
> 1    
> (kjx later oops - clearly that's impossible. It's 2)
> 
>> What is tmp?
> 
> 2
> 
>> var tmp
>> def a = object {
>>   def x = 1
>>   method foo { "hello" }
>>   tmp := self
>> }
>> def b = object {
>>   inherits a
>>   method foo is override { "world" }
>> }
>> Does tmp == a?
> 
> yes
> 
>> Does tmp == b?
> 
> no
> 
>> What is tmp.foo?
> 
> "hello"
> 
>> This should work:
>> method aCreator {
>>   object {
>>     def x = 1
>>   }
>> }
>> def b = object {
>>   inherits aCreator
>>   def y = 2
>> }
> 
> 
> sure 
> 
>> More complicatedly:
>> var tmp
>> method aCreator {
>>   object {
>>     def x = 1
>>     method foo { "hello" }
>>     tmp := self
>>   }
>> }
>> def b = object {
>>   inherits aCreator
>>   method foo is override { "world" }
>> }
>> Does tmp == b?
> 
> oh this one is good!
> I'm going to say "unspecified" - which is bad!
> and then say "yes" which is worse :-)
> 
>> What is tmp.foo?
> 
> unspecified or "world"
> 
>> Or:
>> var tmp
>> method aCreator {
>>   object {
>>     def x = 1
>>     method foo { "hello" }
>>     tmp := { foo }
>>   }
>> }
>> def b = object {
>>   inherits aCreator
>>     method foo { "world" } 
>> }
>> What is tmp.apply?
> 
> unspecified; "world".
> 
>> var tmp := 0
>> method aCreator {
>>   object {
>>     def x = 1
>>     tmp := tmp + 1
>>   }
>> }
>> def a = aCreator
>> def b = object {
>>   inherits a
>> }
>> What is tmp?
> 
> 1
> how could it be otherwise?
> 
>> 
>> var tmp := 0
>> method aCreator {
>>   def ret = object {
>>     def x = 1
>>     tmp := tmp + 1
>>   }
>>   ret
>> }
>> def b = object {
>>   inherits aCreator
>> }
>> What is tmp?
> 
> 1
> 
>> What does a class do here?
> 
> can be understood by desugaring
> 
>> var tmp
>> class aCreator.new { // kjx removed unintended object
>>     def x = 1
>>     method foo is override { "hello" }
>>     tmp := self
>> }
>> def b = object {
>>   inherits aCreator.new
>>   method foo is override { "world" }
>> }
> 
> technically this is an error: aCreator.foo does not override anything.
> 
>> Does tmp == b?
> 
> underspecified - yes .
> 
>> What is tmp.foo?
> 
> "world"
> 
>> var tmp
>> class aCreator.new {
>>     def x = 1
>>     method foo is override { "hello" }
>>     tmp := { foo }
>>   }
>> }
>> def b = object {
>>   inherits aCreator.new
>>   method foo is override { "world" }
>> }
>> What is tmp.apply?
> 
> unspecified - I like "world"
> 
>> class aCreator.new {
>>     def x = 1
>>     method foo is { "hello" }
>>     def y = { foo }
>>   }
>> }
>> def b = object {
>>   inherits aCreator.new
>>   method foo is override { "world" }
>> }
>> What is b.y.apply?
> 
> I like "world"
> The more I think about it, it's really clear this needs to be world
> 
>> var tmp
>> class aCreator.new {
>>   def x = { self }
>>   tmp := x
>> }
>> def b = object {
>>   inherits a
>>   def y = 2
>> }
>> Does tmp == b.x?
> 
> yes
> 
>> Does tmp.apply == b.x.apply?
> 
> yes  follows from the above
> 
>> Here are a few more examples that I forgot:
> 
>> def a = object {
>>   var x := 1
>> }
>> a.x := 2
>> def b = object {
>>   inherits a
>>   def y = 2
>> }
>> What is b.x?
> 
> 2
> 
>> def a = object {
>>   def x = object {
>>     inherits a
>>     def y = 1
>>   }
>> }
> 
> I'd be quite tempted to outlaw that - we haven't talked about it, 
> but my head exploded and there is grey goo dribbling out my ears.
> 
>> Does a.x == a.x.x?
> 
> ^C
> 
> (Ok, actually the answer is yes, and I typed this before looking at the other answers)
> 
>> method aCreator(blk) {
>>     method y { 1 }
>>     def x = blk.apply
>>   }
>> }
>> def b = object {
>>   inherits aCreator { self.y }
>>   method y { 2 }
>> }
> 
> I'm half-tempted to say this should also be a compile-time error 
> because I have weird ideas about where the code to the right of an "inherits" should be executed,
> and where I want it executed doesn't have a "y" method.
> 
> 
>> What is b.x?
> 
> 2 
> 
> (Andrew's extra examples) 
> 
>> 
>> def a =
>> 	{	var tmp := 1
>> 		def result =  object {
>>  				def x = tmp
>>        	}
>> 		tmp := tmp + 1
>> 	}.apply
>> 
>> def b = object {
>>        inherits a
>> }
> 
> as written, a is "done".  Can we inherit from done?  
> If not, - if we add make the last line "tmp := tmp + 1; result"
> Then a gets set to an object with a single "x" def. 
> a inherits from b without reevaluating the outer block;
> because b has no additional methods or anything, b == a 
> 
> OK, now I'll try to look over the other answers.....    long wait... 
> so it's pretty clear my interpretation differs from everyone else's! 
> 
> I wonder if anyone can back-form
> a) my semantics for "inherits"
> and
> b) why I picked that semantics
> 
> James
> 
> 
> 
> _______________________________________________
> Grace-core mailing list
> Grace-core at cecs.pdx.edu
> https://mailhost.cecs.pdx.edu/mailman/listinfo/grace-core



More information about the Grace-core mailing list