[Grace-core] Inheritance and object initialisation

Andrew Black black at cs.pdx.edu
Tue Jul 17 17:32:02 PDT 2012


This has become very long; we should move it to the Wiki.

On Jul 17, 2012, at 10:56 AM, Kim Bruce wrote:

> I'm a little surprised that no one else has weighed in on Michael's questions and my responses.  I'm about to sit down and write down a sketch of the formal semantics of classes/objects if no one has any objections to my interpretation.  On the other hand, it's a fairly tedious task and I don't want to do it if anyone objects strongly to my interpretation. What say you?


I've just seen this discussion; I was (supposedly, ha ha) not working on Monday, and spent this morning on another project.

Kim wrote:

>   I can give a complete formal semantics for classes and objects (which will likely be more accurate than my working through these examples one by one), if everyone agrees with my interpretation of objects as instantiations of anonymous classes and that these anonymous classes are the ones that are actually inherited from.

I don't agree with this.  It seems quite backwards to me.   The whole point of making object constructers first class is that they are simpler than classes; we can derive the semantics of constructor methods from that of objects.
But it's a consequence of this that Grace objects inherit from other objects, not from classes, so we have to answer question like Michael's about object identity.

My original "informal semantics" was that the inherited object was copied into the inheritor.   The main thing missing from this informal semantics is that we never said whether the copy was deep or shallow.  I'm going to assume deep, and I think that this will give a similar set of answers to those that Kim has already given, which are based on re-constrcuting the super-object from its definition each time it is inherited.

I also note that this description of inheritance does not appear in the Spec.  I recall that this was because James didn't want to make copying a primitive operation.

The places that our answers will differ is where the object was created by reading a non-local variable that has since changed, and where self is captured during construction of the super-object.  My semantics will yield a copy of the original value, while Kim's semantics will read the non-local variable again, or re-interpret self.  I see attempting to read the non-local variable again as a problem, since it may no longer exist!

With that preamble, let's look at Michael's examples.

>  def a = object {
>    def x = 1
>  }
>  def b = object {
>    inherits a
>    def y = 2
>  }

b.x = 1 and b.y = 2.

>  def a = object {
>    def x = object { var t := 2 }
>  }
>  def b = object {
>    inherits a
>  }
> Does a.x == b.x?

No, because a is deep-copied into b, so the object bound to x is also copied.  So, as Kim said, a.x and b.x are similar objects, which are indeed observationally equivalent until someone says a.x:=1, after which they are visibly different.

> 
> def a = object {
>   def x = { self }
> }
> def b = object {
>   inherits a
>   def y = 2
> }
> Does a.x == b.x? Does a.x.apply == b.x.apply?


a.x.apply = a and b.x.apply = b, so they are clearly different.  What about a.x and b.x?   Is == defined on blocks?  
I propose that the answer be no, or that is == is defined, then (by our egal rules), all blocks are distinct, so == on blocks always answers false.  This example shows why.   The code for a.x and b.x is likely to be identical in most implementations, because self will be resolved dynamically.  But it's clearly confusing to have two == methods with no parameters return different answers.

So far Kim and I have agreed, I believe.  On Kim's next example, we disagree:

> def a = object {
>   def me = self 
> }
> def b = object {
>   inherits a
>   def y = 2
> } 
> 
> I think it is pretty clear that a.me should refer to a, while b.me should refer to b.  

A agree that a.me should be a.  If you take my deep copy semantics, then b.me should also be a.  I would argue that any other interpretation is wrong, because the definition of me takes place exactly once, in a, and in that context, self means a.

In other words, putting the braces around self delays its evaluation in an object constructor just as braces do everywhere else in the language, and not putting the braces around self means that it is evaluated exactly once.

> var tmp := 1
> def a = object {
>   def x = tmp
> }
> tmp := 2
> def b = object {
>   inherits a
> }
> Does a.x == b.x? What is b.x?


I think that we can all agree that a.x == 1.  By the deep copy semantics, b.x == 1 also.   The fact that tmp is changed after a was created doesn't change the value of a.x.  If the programmer wanted that, then x should have been made a method

> def a = object {
>   method x { tmp }
> }

	
and then method x will have to close over tmp, and fetch tmp's value every time x is executed.  

Kim said that b.x == 2 "because of the delayed evaluation"; rather than deep-copying a, Kim is suggesting that the expression that was evaluated to obtain a be evaluated a second time to construct the super-object  of b.  I don't think that this is reasonable, in part because this means creating a closure containing that expression, where the program doesn't have a closure.

What do we do about this example (a new one that I just thought up)? 

> def a =
> 	{	var tmp := 1
> 		def result =  object {
>   				def x = tmp
>         	}
> 		tmp := tmp + 1
> 	}.apply
> 
> def b = object {
>         inherits a
> }

Now tmp is a local variable of a block, to which no reference is ever captured.  I would expect it to be unreferenced after it has been applied.   Having b inherit a copy of a seems simple; having a be somehow re-created from it's definition seems impossibly weird.  And if it were to be re-created,  what about tmp?  Is it re-created too, or is the old tmp somehow kept around?

Going on to Michael's next example:

> 
> var tmp := 1
> def a = object {
>   def x = tmp * 2
>   tmp := tmp + 1
> }
> def b = object {
>   inherits a
> }
> Does a.x == b.x? What is b.x? What is tmp?

Kim says that a is evaluated before b, hence tmp has been increased before b is evaluated.  Kim says that because the body of a is re-evaluated when inherited, x is 4 and tmp ends up being 3.   

Kim's answer does not seem reasonable to me, because there is noting in the above program that would cause one to guess that the code used to define a is executed more than once.  By the deep-copy semantics,
a.x == 2, and b.x == 2.  tmp is also 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? Does tmp == b? What is tmp.foo?


tmp == a.  The assignment of self to tmp takes place in a context where self is bound to a.  What else could it mean?   So tmp.foo means a.foo, and returns "hello".

I can see an argument that we should disallow "bare" uses of self inside an object literal, that is, occurrences of self not protected by braces.  But if we allow it, the fixpoint has to be taken when the object constructor is evaluated.

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? What is tmp.foo?

Kim says that this is clear, in that the super object is constructed just before b is created, so tmp == b and tmp.foo is "world".   I agree that the super-obejct is created as part of the construction of b.  But that self on line 6 is not protected by braces, so it is evaluated in the context of the object expression inside aCreator, not in the context of b.  So tmp is an object whose foo method answers "hello", which means that tmp ≠ b.

This example show the danger of the "concatenate the text of the definitions" semantics that's in the Spec.  It works for methods, but not for code embedded within the definitions.

> var tmp
> method aCreator {
>   object {
>     def x = 1
>     method foo { "hello" }
>     tmp := { foo }
>   }
> }
> def b = object {
>   inherits aCreator
>   method foo is override { "world" }
> }


Here, the assignment to tmp gives it the value  { self.foo }, so everything turns on the binding of that implicit self.  It seems clear that self refers to an object whose foo method answers "hello", and that's true no matter how many time aCreator is executed.  So tmp.apply is "hello".

> var tmp := 0
> method aCreator {
>   object {
>     def x = 1
>     tmp := tmp + 1
>   }
> }
> def a = aCreator
> def b = object {
>   inherits a
> }

What is tmp?  aCreator is requested just once — that's clear from the syntax, because there are no braces around the method request.   So tmp == 1.   Kim says that it is 2; somehow he wants to execute the first object constructor twice.


> var tmp := 0
> method aCreator {
>   def ret = object {
>     def x = 1
>     tmp := tmp + 1
>   }
>   ret
> }
> def b = object {
>   inherits aCreator
> }

What is tmp now/  It seems clear to me that this is the same as the previous example.  The difference is that 
 <expr> is replaced by 
	def ret = <expr>; ret 
inside aCreator, and that 
	def a = <expr>  ... a ...
is replaced by <expr> in the object constructor for b.  I would certainly hope that these are semantics-preserving refactorings, so long as expr doesn't refer to a variable that's changed in between the def and the use.  In other words, aCreator is still requested just once, so tmp == 1.

Kim then gives this example:

> var tmp := 0
> class aClass.new {
>     def x = 1
>     tmp := tmp + 1
> }
> def b = object {
>   inherits aClass.new
> }

I find the class syntax confusing, because (I think) that the temp:= tmp +1 occurs in the new method, while the
def x = 1 occurs in the object returned by new.  This is in spite of them appearing at the same nesting depth in the syntax.  In other words, I'm interpreting 

> class aClass.new {
>     def x = 1
>     tmp := tmp + 1
> }

as meaning

> def aClass = object {
> 	method new {
>     		object { def x = 1 }
>     		tmp := tmp + 1
> 	}
> }

Perhaps you intended it to mean 

> def aClass = object {
> 	method new {
>     		object { 
> 			def x = 1 
>     			tmp := tmp + 1
> 		  }
> 	}
> }



Still, I agree with Kim that here also, tmp == 1. 

I've corrected the next example, as Kim and Michael suggested:

> var tmp
> class aCreator.new {
>     def x = 1
>     method foo is override { "hello" }
>     tmp := self
>  }
> def b = object {
>   inherits aCreator.new
>   method foo is override { "world" }
> }
> 
> Does tmp == b? What is tmp.foo?


Again, the class syntax may be misleading.  Does the assignment to tmp takes place inside the new method,
or inside the invisible object constructor evaluated by the new method?  The value of self clearly depends on which of these interpretations we adopt.   I believe that  the assignment to tmp takes place inside the new method, and thus self refers to the class object aCreator.  So tmp == aCreator ≠ b  tmp.foo is illegal, since aCreator does not have a foo method.

Minigrace takes the other interpretation, in which self is the object created by aCreator.new, and thus tmp.foo answers "hello".   I don't see any interpretation in which tmp.foo can be "world"

I believe that the next example is also illegal, for the same reason.

> 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" }
> }
> 

The assignment to tmp is a shorthand for 

	tmp := { self.foo }

where self is the class object, not the object created by the factory method.  If you interpret that assignment as being inside the inner object constructor, then self has a foo method that answers "hello".   


> class aCreator.new {
>     def x = 1
>     method foo  { "hello" }  
>     def y = { foo }
> }
> 
> def b = object {
>   inherits aCreator.new
>   method foo is override { "world" }
> }
> What is b.y.apply?


Now it is clear that the def y = { self.foo } is inside the object constructor returned by the new method.  So it has to refer to that object, whose foo method answers "hello".  Hence, by.y.apply == "hello".   

Note that we could have written

> class aCreator.new {
>     def x = 1
>     method foo  { "hello" }  
>     method y = { foo }
> }
> 
> def b = object {
>   inherits aCreator.new
>   method foo is override { "world" }
> }


Now b.y is "world".

I'm a little uncomfortable that these are different, but I think that I can justify it.  The (implicit) self inside method y is bound when y is executed, which takes place as a result of the request b.y, and is hence inside the object b.   However, the implicit self inside 
    def y = { foo }
is bound when the block-closure is constructed, which is inside the object-literal evaluated by new, and thus refers to an object whose foo method answers "hello".

I can see that I might be convinced to change my mind about the meaning of self inside a block closure.   However, I don't think that it makes sense to take a purely dynamic interpretation in which self means whatever object the block closure is evaluated inside.

> var tmp
> class aCreator.new {
>   def x = { self }
>   tmp := x
> }
> def b = object {
>   inherits aCreator.new   // my correction 
>   def y = 2
> }
> Does tmp == b.x? Does tmp.apply == b.x.apply?


Under my interpretation of straight-line code in a class, this is illegal, so I'll assume that the tmp := x is in the object constructor nested inside the new method.  Self refers to that object, which has no y method, so it can't be b.  tmp.apply ≠ b.x.apply as before: these are two objects with different method suites.

Michael writes:

> 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?

At the time that b inherits from a, a.x == 2.  So b.x == 2 also.  Subsequent changes to a.x won't change b.
> 
>  def a = object {
>    def x = object {
>      inherits a
>      def y = 1
>    }
>  }
> Does a.x == a.x.x?

I think that's ill defined, but we need to hammer out the semantics of initialization before i can be sure.  (I was hoping that Michael would help with this). I think that attempting to evaluate a leads to non-termination.


> 
>  method aCreator(blk) {
>    object {
>      method y { 1 }
>      def x = blk.apply
>    }
>  }
>  def b = object {
>    inherits aCreator { self.y }
>    method y { 2 }
>  }
> What is b.x?

I think that's ill-defined too, although b.x == 2 is also reasonable.   

My idea for initialization order is that all of the (def and class) names in a scope come into existence simultaneoulsy, on entry into that scope, but that they are bound to "notYetDefined".   When name bound notYetDefined is used, it's rebound to "beingDefined", the declaration is evaluated, and the resulting value is bound to the name.  If one ever finds a reference to "beingDefined"  while elaborating a declaration, then the definition is circular, and the program is rejected as being ill-formed.

In the first example, a.x forces a to the beingDefined state, and a is still in this state when x tries to inherit from it.

In the second example, b.x forces b into the beingDefined state, and it's still in that state when self.y is evaluated.   So I could argue that self == b == beingDefined.  But perhaps self needs to be interpreted specially?  I can imagine real examples where this kind of recursive definition is useful, that is where the initialization parameters of one's superobject are obtained from one's own methods.

For example

	def window = object  {
		var border = aBorder.ofColor(self.bordercolor)
		...
		method borderColor { Color.black }
	}
 

	def errorWindow = object  {
		inherits window
		...
		method borderColor is override { Color.red }	
	}

The equivalent code would work in Smalltalk, because the inherited instance variables are initialized after the errorWindow object has been created, and at that time self is the errorWindow.  But it won't work in Grace as I've imagined inheritance in this message; the border would always be black.

To do this sort of thing in Grace, the color would have to be a parameter of the constructor:


	def aWindow = object {
		method borderColored(c)  {
			object {
				var border = aBorder.ofColor(c)
				method borderColor { c }
			}
		method new { borderColored(Color.black) }
	}

	def window = aWindow.new

	def errorWindow = object  {
		inherits aWindow.borderColored(self.borderColor)
		...
		method borderColor is override { Color.red }	
	}

This is exactly the situation that Michael cited in his final example.  So maybe we have to come up with something better for initialization order?

	Andrew



-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mailhost.cecs.pdx.edu/mailman/private/grace-core/attachments/20120717/1e891ab9/attachment-0001.html>


More information about the Grace-core mailing list