[Grace-core] Encoding classes

Kim Bruce kim at cs.pomona.edu
Fri Jul 15 14:34:33 PDT 2011


[THIS IS LONG BUT IMPORTANT -- PLEASE READ!]

This conversation led me to take a closer look to see what prior research has done with encoding classes as objects.  Not surprisingly, the definitive research is by Abadi and Cardelli, and is explained in section 8.5 of their book, "A theory of objects".

Let's work with yesterday's example where we want pair objects with type:

type Pair {mkDiffPt(dx:Number,dy:Number) -> Pair}

Let's suppose we want a class that uses a hidden method called helper with type A -> B.  If all we want to do is instantiate objects then the encoding with just a method "new" works fine.  

However, if we want to inherit, we have to do quite a bit more.  The difficulty is that an object is a fixed point, where self represents the entire object.  A class can be seen as a generator of that fixed point.  When we define a subclass, we are actually extending the generator of the fixed point (object), not the fixed point (object) itself.  

Thus we need to keep hold of the methods before self has been instantiated as a fixed point.  As a result our "class object" will not only need a "new" method, but it will also need separate copies of each of the methods (public and protected) that have an explicit self parameter. 

Thus the encoding of the class as an object will look as follows (where I've left out all code that actually does anything!)

const PairClass = object {
	method new:(x:Number, y: Number) -> Pair {
		object{
			var x: Number
			var y: Number
			method mkDiffPt(dx:Number,dy:Number) -> Pair { ...  helper ... }
			<private> method helper(a:A) -> B {...}
		}
	}

	// returns a closure to be used later in the body of a method mkDiffPt to be inserted in a subclass
	method mkDiffPt'(s:Point)-> { (dx:Number,dy:Number) -> Pair} {
		{(dx:Number,dy:Number) -> Pair {.....  helper ... }}

	// same idea for helper
	method helper'(s:Point)-> { (a:A) -> B} {
		{...} }
}

You will notice that the type of PairClass is given by

type PairClassType {
	new: (x:Number, y: Number) -> Pair
	mkDiffPt': (s:Point)-> { (dx:Number,dy:Number) -> Pair}
	helper'(s:Point)-> { (a:A) -> B}
}

I won't write out the entire encoding in detail for you here, but to construct a subclass of PairClass, you add in any new methods (explicitly parameterized by self -- as with the last two methods above) and you then replace the inherited "new" method by one that returns an object that is formed by assembling together each of the inherited "primed" methods (each applied to the "self" of this new object) and adding on and/or replacing methods given by the subclass.  

Here is an example of part of what would have to be done to create the update "new" method for a subclass adding a color field

const ColorPairClass = object {
	method new:(x:Number, y: Number,c:Color) -> ColorPair {
		object{
		   var x: Number   // the x,y would not actually appear as they would be replaced by getter
		   var y: Number   // and setter methods but let's ignore that here
		   var c: Color
			
		   method mkDiffPt(dx:Number,dy:Number) -> Pair {PairClass.mkDiffPt'(self).app(dx,dy) }
		   <private> method helper(a:A) -> B {...}
		}
	}

	method mkDiffPt': (s:Point)-> { (dx:Number,dy:Number) -> Pair} {...}
	method helper'(s:Point)-> { (a:A) -> B} {...}
	... // other methods corresponding to new methods of subclass

}


I leave it as an exercise for the Grace programmer to encode this new subclass properly as an object using only legal Grace code.  (To make it simple, just add a method that returns the sum of the two numbers in the pair.)

I suspect that some of this painful re-encoding of the new method could be made to go away by defining an "extends" operator that does a lot of this gluing together, but even if that is the case, we're still looking at something quite complex, with the "shadow" copies of all methods (hidden and public) showing up in the object encoding.

So we have both good news and bad news:

Good news:  Classes can be encoded as objects in ways that respect the type system.  

(Caveat -- I didn't write out all the details of the encoding and I'm not even sure how to write a method that returns a closure -- would writing it with {{ ... }} be proper?)

Bad news:  No one in their right mind would want to write out classes by hand in this way.  (Can you imagine how helpful the error messages would be if your wrote it with this encoding and made a slight mistake?)

To get a properly-type encoding of classes as objects in their object calculus takes a fair amount of work, and is significantly more complex than the one we've been talking about.

There may be an alternative type-safe way of doing this encoding (there are a few simple modifications that I see, but they don't simplify things much), but I suspect that overall they will be of the same complexity as this.

So, if we want to continue to treat classes as encodings we need to:

1.  Work out all the gory details of any proposal to ensure that it works in a type-safe way.

2.  Convince ourselves that the results is (nearly as) understandable as the more traditional way of handling classes.

Unless we can do this, I highly recommend that we scrub the idea of encoding classes as objects and just have classes as primitives.  (Of course it is trivial to write objects as encodings using classes -- just write the obvious class with parameterless constructor and invoke new.)

Kim



On Jul 15, 2011, at 2:34 AM, James Noble wrote:

>> I continue to worry about whether we have a type-safe encoding of classes as objects with new methods.  Has anyone worked out the details?
> 
> Nope...
> 
>> However, we notice that all classes generating Pairs that have a constructor taking a pair of numbers have exactly the same type.  This may be fine, but is going to cause difficulty if our class has non-public methods.  For example, suppose PairClass has a private method "helper".
>> 
>> The problem arises when I want to override "helper":
>> 
>> class ExtPairClass {x, y -> extends PairClass.new(x,y)
>>                                    <override> method helper(...) -> ... {...}
>> }
>> 
>> As I see it, the type system has no way of knowing whether PairClass has a helper method or not and hence whether or not the override is legal.  In particular, if I replace all occurrences of PairClass in the above by another class that also has type PairClassType (but no helper method), it should type check with exactly the same result.
> 
> This is why we (currently)may only write "extends Classname.new(x,y)" 
> Irrespective of the encoding (or otherwise) used to implement factories and classes,
> so long as the subclass declaration (ExtPairClass) can only extend a manifest
> superclass (PairClass) I don't see any problem.
> 
> Even if the private methods aren't in the type either of the factory or the instance objects
> produced by the PairClass class declaration, ExtPairClass can still consult the PairClass
> _declaration_ and get all the information there. 
> 
>> My point is that Classes are more than constructors and the public methods.  In my languages, the types of classes including all protected features as well as those that were public.  I'm not at all sure how that can be done with this encoding.
> 
> Right. It's not just override, there may be other annotations (we will need final or some analogue of that,
> at least for some of the earlier "language levels") with consistency conditions across subclasses.
> This is why I think classes can only inherit from classes.  If we want object extension, for practical reasons,
> objects should be restricted to inherit only from classes too. 
> 
>> My conceptual model is like this: an object understands request for ALL of its methods, even the protected ones ("private" in your terminology).  So a class with a method _helper has a different type from a class without the method _helper.  
> 
> We could do it that way - I guess we have to decide, I don't know what's best.
> I guess the question is: do we put *all* the information about a class into its type, or not? 
> 
>> (I don't recall if we settled on the prefix _  meaning protected or not).
> 
> No we didn't, and we didn't garner any discussion on the blog.
> I had a few emails about it however, all of which made good points,
> none of which agreed...
> 
> James

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mailhost.cecs.pdx.edu/mailman/private/grace-core/attachments/20110715/8ff1cfe1/attachment.html>


More information about the Grace-core mailing list