[Grace-core] Semantics of object/classes

Kim Bruce kim at cs.pomona.edu
Thu Aug 9 13:15:51 PDT 2012


Not surprisingly, my original (and continuing) point of view was quite different from Andrew's.  I think of objects as mainly being created from classes and see a useful distinction between the semantics of the two.  Classes are extensible generators of objects, but aren't necessarily objects themselves.  It is useful to have a short-hand object definition when you are only going to need one object with a particular shape, but I see that as the special case, not the general case for large software projects.

I was extremely uncomfortable with the notation we adopted for inheritance when it first arose and tried hard to change it.  I eventually reconciled myself to it, arguing (to myself) that we could disaggregate the pieces when it came time to do the semantics.  I welcome the opportunity to talk about a different syntax that better reflects the semantics.

In terms of the fundamental issues, I (not surprisingly) have some differences with Andrew.  While I agree 100% that the public interface of an object is the methods, I believe we all think of objects as consisting of behavior and state.  Put differently, objects will need an initial state, and that state may be changed via method requests on the object.

When we create objects we need to allocate the state, initializing it appropriately, and install the behaviors by associated actions with the method names.  In some ways, Eiffel's object creation syntax mimics the two phases of allocating state and initializing it.  For Eiffel, if "make" is a "creation" method for class C, and c is a variable of type (class) C, then one would write !!c.make(...) to create and initialize c.

We would want to write this differently in Grace, perhaps something like !!C.make(...) to create and initialize the object (since classes aren't types for us), which could then be assigned to a variable or used in some other expression.  This kind of notation would perhaps make it clearer that the "make" method is being run after the object is created, so of course self would refer to the object that has just been created.  (Note:  I'm not that fond of "!!", just noting the advantages of having notation that separates the phases.)

Now there are complications to having a separate initialization routine -- the same kind of things that Java had to deal with.  What is the order of initialization when some things can be initialized in their declarations while others are executed in the initialization code (and Java has even more variants).  Extra complications come with Java "final" declarations -- and equivalently our "def"s.  Hence Java's rules on when these have to be initialized.

I liked the fact that we didn't have to face as complicated issues as our initialization code is mixed with the declarations.  Of course we could still do that with a slightly different notation:

class C with init(a,b,c) extending D running otherInit(a,b) {
   ...
}

That's too long and awkward (and I'm sure it can be improved), but you get the idea.  

My concerns:

1.  We're going to have a very hard time getting instructors to adapt a new language of any sort.  One major claim we have made is that it will be easy for students to move from this language to other common OO languages:  Java, C#, Smalltalk, Scala.  If the semantics is different in ways that the instructors don't find useful, it gives them a powerful (additional) reason to say no.

2.  Initialization of objects defined using inheritance seems to be more useful if "self" means the whole object.  The current implementation is preventing me from doing what I want, and I don't see any advantages to doing things that way.  

If the syntax is confusing, let's change the syntax.  That shouldn't be the determining factor.  While I was unhappy about the syntax originally, I didn't realize that we were actually thinking of very different semantics.  I wish I had realized it earlier as I would have objected then.

Talk to you both soon.

Kim



On Aug 9, 2012, at 8:23 AM, Andrew P. Black wrote:

> 
> On 8 Aug 2012, at 23:11, James Noble wrote:
> 
>> On 9/08/2012, at 13:57 PM, Marco Servetto wrote:
>> 
>>>> object { inherits X D... }  //  "inserts" all parts of the object described by D into X
>>> 
>>> For a complete "inherit from objects" semantic, this design require
>>> objects to be dynamically enriched.
>> 
>> if we allow general inheritance from any object, yes.
>> If only from some kind of "factory method" or "class" that 
>> encapsulates and returns an object constructor, then no.
> 
> 
> I think that Marco is exactly right.   I spent a lot of time laying away last night thinking about this, and some things that I have known for a long time came into sharp focus.
> 
> In Smalltalk, classes serve 8 different rôles, one of which is manufacture of its instances, another of which is a template for those instances, and a third being the repository for the instances' methods.
> 
> In Grace, classes serve just the first role.  Objects own their one methods, and don't have templates.  This means that it makes no sense to talk about "inheriting from a factory method".  The factory method doesn't contain the information that you need to inherit — unless you believe that you can extract that information from an analysis of the code in the factory method!
> 
> That was why we inherit from objects: objects own their own methods, so it makes sense to ask an object for its methods, and copy them into a new object.  This is rather like trait-based reuse, except for one vital difference.  In the trait-based languages that I know (which means Smalltalk and Fortress, but not Scala), traits can't have lexical bindings.   In Grace they can.  Hence the careful wording about what happens to lexical binding in my description of Grace inheritance.
> 
> Let me digress for a moment, and point out that I have a _lot_ of difficulty interpreting something that does not mean what it appears to mean.   It's related to  Aspergers Syndrome, I think:  I take wording literally.  This is one reason, perhaps, that I am keen for Grace's syntax to mean what it appears to mean.   As a consequence, I really can't grok 
> 
>>>> object { inherits X D... }  //  "inserts" all parts of the object described by D into X
> 
> If that's what we mean, then we should rite something like
> 
>> X becomes X extended by { D }
> 
> 
> assuming that X is a pre-existing object.
> 
> I don't mind changing the semantics, but if we do, we should change the syntax too.  It will help in our discussions.
> 
> I have a similar difficulty with James's MinimalFactoryInheritance page.  He wrote:
> 
>> The short story is very simple: all of the declarations of the super class, car in the above example, are added to the object being constructed by the object constructor that inherits from that super class
> 
> But the inheritance is not from Car — it's from Car.new.      bicycle could inherit from Car, but that would mean something quite different.   Car.new is not a class, it's an object (that models a car).   Car is a class — an object that models a car factory.  You can choose which one you inherit from, but you have to be consistent.
> 
> Assuming that we do actually mean to inherit from the instance Car.new — and thats where the methods, live, so it seems like a reasonable expectation — what's in that object.  Methods.   Just methods.   Grace objects don't have "slots" or "fields", and never have.   The three declarations, for position, speed and heading, are private to the car object, and the related variables get "dragged along" by the methods that lexically bind to them.  (Since the Claremont meeting, this has been any method in the object; before Claremont, it was just the pair of accessor methods, and if we annotate the var as public, then it's still the accessor method(s).)
> 
> Why does this matter?  It matters because it affects how one designs an object for reuse.  Reuse in Grace is different from reuse in Java (and in Smalltalk) because our objects work differently.   In Grace, if an object  j def's wombat and method frobbit accesses wombat lexically, then the designer of the object DECIDED TO REFUSE TO ALLOW THAT ACCESS TO wombat BE CHANGED by inheriting objects.   It's Grace's version of final.   
> 
> If one wants to reuse the object in spite of these efforts by its designer, then the solution is (in an object jk) to override the method frobbit so that it no longer accesses wombat.   If we assume that frobbit was the ONLY method in k that accesses wombat, then wombat will no longer be "dragged along" into object jk that inherits from k.    Alternatively, one can refactor j so that frobbit no longer accesses wombat lexically.  For example, one might make the def of j confidential, which means that accesses from frobbit are now method requests, and are therefor overridable.  sub-objects like jk could then  declare a new constant, say wombat2, and provide a method wombat to access it; now the frobbit method in k is accessing wombat2, though a method request that is dynamically bound, rather than through a lexically bound variable reference.  The important point to note here is that jk does not have to provide any storage for wombat:  it's not used, so it's not "dragged in".
> 
> The reason that I'm so opposed to having sub-objects inherits the initialization code of their super objects is that the super-objects DON'T CONTAIN that initialization code.  They don't contain it any more than after the execution of
> 
> 	def area = length * width
> 	def volume = area * height
> 
> volume contains the arithmetic that computed its value.   If length changes, volume does not.
> 
> This is different from methods.  The code for methods IS in the object.  Methods are more like
> 
> 	def volume = { def area  = length * width ;  area * height }
> 
> Now, if we assign to length and then request volume.apply, the new value of length will indeed be used: that's the meaning of making volume a block.
> 
> Hence, my proposal that we put initialization code in a method, so that the code IS available to inheritors, and can be executed if and when the sub-objects request it.  I don't understand James's claim the the initialization of the super-objects will be performed multiple times, but even if it is, so what?
> 
> Remember that the motivation for object constructors was the ability to create "fully formed" objects.  The consequence of that is that the objects are full-formed when they are created.  There is no initialization code in a Grace object right now, which is why we can't "re-execute" it.  
> 
> The idea that the meaning of the message request
> 
> 	Car.new
> 
> should somehow depend on the bindings that are in existence in the lexical scope SURROUNDING the message request Car.new has been thoroughly explore in languages starting with an implementation mistake in the original LISP.  It's called dynamic scope.   It's generally considered to be a bad idea, but some languages have revived it for special occasion usage — Clojure is one example.  Clojure at least has the wisdom to say explicitly
> 
> (def ^{:dynamic true} x 2)
> 
> if x is to permit dynamic re-binding, and to say explicitly (binding [x 10 ] <code to be executed in the scope of the dynamic binding for x)
> 
> There may be a reason to put dynamic banding in a higher-languge-level of Grace, just so that people teaching the PL class can explain how dynamic binding works without having to switch language.  (The same reason as having matches).  If we do eventually add it, I hope it ill be just as explicit as it is in Clojure.
> 
> Now I really must get back to 2 overdue paper ...
> 
> 	Andrew
> 
> 
> 
> 
> 
> 
> 
> 
> 
> _______________________________________________
> 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