[Grace-core] Another inheritance/initialisation example - invariants

Andrew P. Black black at cs.pdx.edu
Tue Jul 31 12:49:58 PDT 2012


On 29 Jul 2012, at 03:56 , James Noble wrote:

> But with delegation semantics, the blocks will be bound
> to the "self" that holds only the "superclass" portion of the object;
> with concatenation+copy the blocks will be bound to part-objects
> that are unrelated to the actual whole-objects.   I can't see easy
> ways to make this work, unless we start allowing blocks (not
> closures but the block objects) to be cloned and somehow
> *rebound* to different objects.  So when cloning an object
> with invariants, we'd have to clone its blocks and reattach
> them to the clone.  This all seems rather hard to me - 
> but if there are designs that can make this work with nonclassical
> semantics, I'd love to hear them. 

This is a good point, I think.  Let's see if I understand.

On the one hand, suppose that I have objects

	def account = object {
		var myBalance := 0
		method balance { myBalance }
		method isOverdrawn { self.myBalance < 0 }
	}

Notice that the predicate/invariant is defined as a method, and that both methods access myBalance lexically.  I'm putting in the 'self's to be pedantic.


	def currentAccount = object {
		inherits account
		method monthlyFee {
			if (self.isOverdrawn) then {self.balance * 0.05}
				else {- (self. balance * 0.01)}
		}
	}

The idea is that currentAccount is a special kind of account that pays interest, unless the account is overdrawn, in which case a fee is charged.  WIll currentAccount.monthlyFee compute the correct amount?

The code above will work fine with delegation semantics, because we are happy that self in the inherited isOverdrawn method is bound to currentAccount, not account.

On the other hand, suppose that I have objects

	def account' = object {
		var myBalance := 0
		method balance { myBalance }
		def isOverdrawn is readable = { myBalance < 0 }
	}

Notice that here the predicate/invariant is defined as a block.

	def currentAccount' = object {
		inherits account'
		method monthlyFee {
			if (self.isOverdrawn.apply) then {self.balance * 0.05}
				else {- (self.balance * 0.01)}
		}
	}

Let's ignore for now the question of why someone might choose to use a block rather than a method — blocks are in the language, so this ought to work.

To make it work, James asks how we clone the account' object when we start to build the currentAccount' object.  What happens to the account'.isOverdrawn block object?   If clone is a shallow copy, whether or not the Number bound to myBalance is shared is irrelevant, but whether or not the block object bound to isOverdrawn is shared is clearly NOT irrelevant.  It seems clear to me that the block object "ought" to close over the self that is the new object.  Which means that the blocks objects in account' and currentAccount can't be the same objects.

What about the methods in account and currentAccount?   Well, they have to close over the right myBalance, so they can't be the same either.  For both the methods and the blocks, while the code can be shared between the two objects, the free variable list can't be.

To realize why this must be so, recall that Grace has a rather different notion of encapsulated data than the "instance variables" of Smalltalk or the "fields" of Java.  The data encapsulated by a Grace object are exactly the variables that are used free in the methods and blocks of the object.    An object can have no defs inside it, but still capture data; our favorite point example shows this.

	class cartesianPoint.x(xCoord)y(yCoord) {
		var arbitrary := 7.348			// not useful, here just to illustrate a point.
		method x {xCoord}
		method y {yCoord}
		method distanceTo(other) { (((x-other.x)^2) + ((y-other.y)^2)).sqrt }
		method r { ((x^2) + (y^2)).sqrt }
		method theta { (y/x).arctan }
	}

What data does cartesianPoint.x 2 y 3  encapsulate?  Clearly 2 and 3, or else the other methods won't work.   It does not have to encapsulate 'arbitrary', because we know that that variable, being private is never accessed.

So, cloning an object means that the clone mechanism has to be able to walk the free variable list of the method and block code.

Incidentally, the work that I did with Haskell last year was predicated by the fact that Haskell, in it's fear of reflection, does not provide a way of walking the free variable list of a function.  How then does one marshal  function across the network?    I deduce that Grace's reflection facilities _must_ provide access to the free variable list of an object — and this is so independent of the semantics we adopt for inheritance.

For me, James' example clarifies what the semantics of Grace inheritance must be.  The issue becomes how to implement it.

	Andrew



	



More information about the Grace-core mailing list