Com S 342 meeting -*- Outline -*- * Language for OOP (5.3) Make copies of figures 5.8, 5.10, 5.12, 5.13, 5.14 The interpreter for section 5.3 is in ch5-3.scm ** syntax ------------------------------------------ SYNTAX ::= {}* ::= class extends {field }* {}* ::= method ( {}*(,) ) ::= ... | ------------------------------------------ Q: What is the extends for in a class declaration? Q: What do we do if the class doesn't have a superclass? Q: Does it ever make sense to omit new fields? New methods? Q: What kinds of new expressions do we need? ... new ( {}*(,) ) | send ( {}*(,) ) | super ( {}*(,) ) Q: What would be the abstract syntax appropriate for these? (show them in the interpreter) (define-datatype program program? (a-program (class-decls (list-of class-decl?)) (exp expression?))) (define-datatype class-decl class-decl? (a-class-decl (class-name symbol?) (super-name symbol?) (field-ids (list-of symbol?)) (method-decls (list-of method-decl?)))) (define-datatype method-decl method-decl? (a-method-decl (method-name symbol?) (ids (list-of symbol?)) (body expression?))) (define-datatype expression expression? ;; ... (new-object-exp (class-name symbol?) (rands (list-of expression?))) (method-app-exp (obj-exp expression?) (method-name symbol?) (rands (list-of expression?))) (super-call-exp (method-name symbol?) (rands (list-of expression?))) ) ** semantics The interpreter is built on the 3-7.scm code *** domains ------------------------------------------ DOMAINS Expressed-Value = Number + ProcVal + Obj + List(Expressed-Value) + Symbol Denoted-Value = Ref(Expressed-Value) Code for expressed values: (define-datatype Expressed-Value expval? (number->expressed (number number?)) (procval->expressed (procval procval?)) (object->expressed (object object?)) (list->expressed (list (list-of expval?))) (symbol->expressed (symbol symbol?)) ) ------------------------------------------ Obj is the domain of objects, which will differ in different implementations Technically we need to add symbols, because we will be storing a symbol for the name of the current superclass in the environment. I also added a "quote" expression so that users can also use symbols as data. *** programs ------------------------------------------ EVAL-PROGRAM (deftype eval-program (-> (program) datum)) (define eval-program (lambda (pgm) (cases program pgm (a-program (c-decls body) (elaborate-class-decls! c-decls) (expressed->printable (eval-expression body (init-env))))))) ------------------------------------------ Q: why do we need the call to elaborate-class-decls!? Have to set up some sort of global table or environment to keep track of information declared in the classes, otherwise that would not be available to expression evaluations. *** expressions 3 new cases in eval-expression (+ quote) ------------------------------------------ EVAL-EXPRESSION (deftype eval-expression (-> (expression environment) Expressed-Value)) (define eval-expression (lambda (exp env) (cases expression exp (quote-exp (sym) (symbol->expressed sym)) ;; ... (new-object-exp (class-name rands) (method-app-exp (obj-exp method-name rands) (super-call-exp (method-name rands) ))) ------------------------------------------ Develop the following... ... (new-object-exp (class-name rands) (let ((args (eval-rands rands env)) (obj (new-object class-name))) (ignore (find-method-and-apply 'initialize class-name obj args)) (object->expressed obj))) (method-app-exp (obj-exp method-name rands) (let ((args (eval-rands rands env)) (obj (expressed->object (eval-expression obj-exp env)))) (find-method-and-apply method-name (object->class-name obj) obj args))) (super-call-exp (method-name rands) (let ((args (eval-rands rands env)) (obj (expressed->object (apply-env env 'self)))) (find-method-and-apply method-name (expressed->symbol (apply-env env '%super)) obj Q: What other procedures has to be written to make these work? elaborate-class-decls! find-method-and-apply object->class-name new-object Q: What are these supposed to do? Q: What are their types? (deftype elaborate-class-decls! (-> ((list-of class-decl)) void)) (deftype find-method-and-apply (-> (symbol symbol object (list-of Expressed-Value)) Expressed-Value)) (deftype object->class-name (-> (object) symbol)) (deftype new-object (-> (symbol) object)) ** four implementations (5.4) A sequence of increasingly more realistic implementations that have better performance properties goal: help students get some idea of the efficiency/pragmatics of OO languages *** a simple implementation (5.4.1) idea: class declarations are to contain the information needed, so represent classes and methods by their abstract syntax trees. see ch5-4-1.scm **** elaborating class declarations ------------------------------------------ A SIMPLE IMPLEMENTATION (5.4.1) (deftype the-class-env (list-of class-decl)) (define the-class-env '()) (deftype elaborate-class-decls! (-> ((list-of class-decl)) void)) (define elaborate-class-decls! (lambda (c-decls) ------------------------------------------ ... (set! the-class-env c-decls))) Q: What should lookup-class do? **** objects ------------------------------------------ REPRESENTING OBJECTS (define-datatype object object? (an-object ------------------------------------------ ... (parts (list-of part?)))) (define-datatype part part? (a-part (class-name symbol?) (fields vector?))) Q: How would you write object->class-name?? (deftype object->class-name (-> (object) symbol)) (define object->class-name (lambda (obj) (cases object obj (an-object (parts) (part->class-name (car parts)))))) Q: Given this, how would we build an object? ------------------------------------------ (deftype new-object (-> (symbol) object)) (define new-object (lambda (class-name) (an-object ------------------------------------------ ... (if (eqv? class-name 'object) '() (let ((c-decl (lookup-class class-name))) (cons (make-first-part c-decl) (object->parts (new-object (class-decl->super-name c-decl))))))))) Q: What should class-decl->super-name do? how would you write it? (deftype class-decl->super-name (-> (class-decl) symbol)) (define class-decl->super-name (lambda (c-decl) (cases class-decl c-decl (a-class-decl (class-name super-name field-ids m-decls) super-name)))) The interpreter uses lots of these kind of "->" accessors. (show them where they live in the file) **** find-method-and-apply Q: ------------------------------------------ (deftype find-method-and-apply (-> (symbol symbol object (list-of Expressed-Value)) Expressed-Value)) (define find-method-and-apply (lambda (m-name host-name self args) ------------------------------------------ Q: What is host-name? It's the object in which to start the search for the method; it's not necessarily the receiver (in the case of a super call). ... (if (eqv? host-name 'object) (eopl:error 'find-method-and-apply "No method for name ~s" m-name) (let ((declared-methods (class-name->method-decls host-name))) (if (has-method-decl? m-name declared-methods) (let ((m-decl (lookup-method-decl m-name declared-methods))) (apply-method m-decl host-name self args)) (find-method-and-apply m-name (class-name->super-name host-name) self args)))))) I added has-method-decl? has a procedure, so this could type check. The disadvantage of this is that we process the list twice, compared to the code in the book, which gives a constant time slowdown. Of course, you would actually use some technique like the one in the book for efficiency. ------------------------------------------ APPLY-METHOD (deftype apply-method (-> (method-decl symbol object (list-of Expressed-Value)) Expressed-Value)) (define apply-method (lambda (m-decl host-name self args) ------------------------------------------ Q: What's the difference between this and find-method-and-apply? Q: What progress have we made? we've actually selected the method declaration to execute Q: What information do we need from the method declaration to execute it? Q: What environment bindings do we need for the method execution? Actuals to formals, of course. Also need the fields of the object to be in the environment, and... Q: How do %super and self get out? this is the place we have to do that binding (why?) ... (let ((ids (method-decl->ids m-decl)) (body (method-decl->body m-decl)) (super-name (class-name->super-name host-name))) (eval-expression body (extend-env (cons '%super (cons 'self ids)) (cons (symbol->expressed super-name) (cons (object->expressed self) args)) (build-field-env (view-object-as (object->parts self) host-name))))))) ------------------------------------------ GETTING THE FIELDS IN THE ENVIRONMENT (deftype build-field-env (-> ((list-of part)) environment)) (define build-field-env (lambda (parts) ------------------------------------------ Q: What kind of recursion is this? Q: Why do we have to recur over a list of parts? to get contributions from super class declarations ... (if (null? parts) (empty-env) (extend-env-refs (part->field-ids (car parts)) (part->fields (car parts)) (build-field-env (cdr parts)))))) Q: Where do we start this recursion? with the host-name, so that we don't get the fields of subclasses involved. Host-name at this point is determined by find-method-and-apply, although this isn't true for later interpreters... ------------------------------------------ (deftype view-object-as (-> ((list-of part) symbol) (list-of part))) (define view-object-as (lambda (parts class-name) ------------------------------------------ ... (if (eqv? (part->class-name (car parts)) class-name) parts (view-object-as (cdr parts) class-name)))) Q: Why does this work? draw a picture like that in figure 5.10 *** flat objects (5.4.2) The first implementation took the path of least resistance in every case. The next three implementations try to optimize things to correspond more to reality. see ch5-4-2.scm **** objects ------------------------------------------ FLAT OBJECTS (5.4.2) New representation: (define-datatype object object? (an-object (class-name symbol?) (fields vector?))) Layout example (c3 < c2 < c1): -c1-- c2 -c3-- x y y x z [an-object | c3 | *-]-> [ | | | | ] ------------------------------------------ Q: If c3 had a subclass, where would its fields appear? To the right. Q: Do the fields of class c1 appear in the same index positions in objects in all of its subclasses? Yes, that's the key property in this representation. Q: So how does searching work for variable names? have to find the rightmost occurrence of the variable name in the environment. See environment-5-4-2.scm for details. ------------------------------------------ NEW-OBJECT (deftype new-object (-> (symbol) object)) (define new-object (lambda (class-name) (an-object class-name (make-vector (roll-up-field-length class-name) (number->expressed 0))))) (deftype roll-up-field-length (-> (symbol) number)) (define roll-up-field-length (lambda (class-name) (if (eqv? class-name 'object) 0 (+ (roll-up-field-length (class-name->super-name class-name)) (length (class-name->field-ids class-name)))))) ------------------------------------------ Adding the initial value as the argument to make-vector was necessary due to the use of an explicit data type for expressed-values. Otherwise, this is unchanged. **** classes unchanged in this interpreter **** methods methods themselves are unchanged, however apply-method needs to change because objects are changed ------------------------------------------ APPLY-METHOD (deftype apply-method (-> (method-decl symbol object (list-of Expressed-Value)) Expressed-Value)) (define apply-method (lambda (m-decl host-name self args) (let ((ids (method-decl->ids m-decl)) (body (method-decl->body m-decl)) (super-name (class-name->super-name host-name)) (field-ids (fields (eval-expression body (extend-env (cons '%super (cons 'self ids)) (cons (symbol->expressed super-name) (cons (object->expressed self) args)) ------------------------------------------ ... (roll-up-field-ids host-name)) ... (object->fields self))) ... (extend-env-refs field-ids fields (empty-env))))))) ------------------------------------------ ROLL-UP-FIELD-IDS (deftype roll-up-field-ids (-> (symbol) (list-of symbol))) (define roll-up-field-ids (lambda (class-name) ------------------------------------------ Q: What's the purpose of this procedure? To produce a list of field names with the most super class's names at the far left and the most specific classes names at the far right Q: What are we recursing over here? The subclassing structure, using these symbols to keep track of where we are. This is an interesting kind of recursion pattern, which will be useful in homework. ... (if (eqv? class-name 'object) '() (append (roll-up-field-ids (class-name->super-name class-name)) (class-name->field-ids class-name))))) Q: When do we search the chain of superclasses? when building objects, in new-object, and invoking methods in apply-method Show figure 5 .12 *** moving the work to class-declaration time (5.4.3) want to do the work of roll-up-field-ids once, when the class is created, instead of each method call Thus need a data structure for classes and methods see ch5-4-3.scm **** classes Note that these are different in class declarations; these are "compiled" in some sense ------------------------------------------ CLASSES (define-datatype class class? (a-class (class-name symbol?) (super-name symbol?) (field-length integer?) (field-ids (list-of symbol?)) (methods method-environment?))) (define method-environment? (list-of method?)) ------------------------------------------ Q: What should the field "methods" hold? In this representation, just the methods declared in the class itself. Q: When do we need to build classes? At the beginning of the program... ------------------------------------------ ELABORATE-CLASS-DECLS! (deftype the-class-env (list-of class)) (define the-class-env '()) ;; ... (deftype elaborate-class-decls! (-> ((list-of class-decl)) void)) (define elaborate-class-decls! (lambda (c-decls) (initialize-class-env!) (for-each elaborate-class-decl! c-decls))) (deftype elaborate-class-decl! (-> (class-decl) void)) (define elaborate-class-decl! (lambda (c-decl) (let ((super-name (class-decl->super-name c-decl))) (let ((field-ids (append (class-name->field-ids super-name) (class-decl->field-ids c-decl)))) (add-to-class-env! (a-class (class-decl->class-name c-decl) super-name (length field-ids) field-ids (roll-up-method-decls c-decl super-name field-ids))))))) ------------------------------------------ Q: What does this say about the order in which classes must be declared in a program? **** methods The key idea is to keep track of the entire list of fields available to the method (in the least to most specific class ordering) ------------------------------------------ METHODS (define-datatype method method? (a-method (method-decl method-decl?) (super-name symbol?) (field-ids (list-of symbol?)))) (deftype roll-up-method-decls (-> (class-decl symbol (list-of symbol)) (list-of method))) (define roll-up-method-decls (lambda (c-decl super-name field-ids) ------------------------------------------ Q: What is this procedure supposed to be doing? It returns a list of method (structures) for the given class. Q: What information could we use to produce a list of methods? We can get the list of method declarations from the class declaration ... (map (lambda (m-decl) (a-method m-decl super-name field-ids)) (class-decl->method-decls c-decl)))) ------------------------------------------ FIND-METHOD-AND-APPLY (deftype find-method-and-apply (-> (symbol symbol object (list-of Expressed-Value)) Expressed-Value)) (define find-method-and-apply (lambda (m-name host-name self args) (let loop ((host-name host-name)) (if (eqv? host-name 'object) (eopl:error 'find-method-and-apply "No method for name ~s" m-name) ------------------------------------------ The only difference is below or that we are dealing with method structures instead of method declarations. ... (let ((declared-methods (class-name->methods host-name))) (if (has-method? m-name declared-methods) (let ((meth (lookup-method m-name declared-methods))) (apply-method meth host-name self args)) (loop (class-name->super-name host-name)))))))) This recursion up the superclass chain to find a method is a key point. apply-method is pretty much the same, except that the helping procedures have different names reflecting the fact that we're not dealing with method declarations, but rather dealing with method structures: ------------------------------------------ APPLY-METHOD (deftype apply-method (-> (method symbol object (list-of Expressed-Value)) Expressed-Value)) (define apply-method (lambda (method host-name self args) (let ((ids (method->ids method)) (body (method->body method)) (super-name (method->super-name method)) (field-ids (method->field-ids method)) (fields (object->fields self))) (eval-expression body (extend-env (cons '%super (cons 'self ids)) (cons (symbol->expressed super-name) (cons (object->expressed self) args)) (extend-env-refs field-ids fields (empty-env))))))) ------------------------------------------ Q: How is this better in terms of getting the list of field identifiers? It uses method->field-ids on the method, which reuses the work done at class construction time, instead of doing the class search at time of applying the method. Q: Is the host-name argument used in apply-method? No **** objects ------------------------------------------ OBJECTS Representation is unchanged. (deftype new-object (-> (symbol) object)) (define new-object (lambda (class-name) (an-object class-name (make-vector (number->expressed 0))))) ------------------------------------------ Q: How can we find out how long the vector should be? Reuse the information put in the class object. This avoids the other efficiency problem we had. ... (class-name->field-length class-name) *** flat method environments (5.4.4) see ch5-4-4.scm **** classes Want to make searching for methods faster... ------------------------------------------ CLASSES (define-datatype class class? (a-class (class-name symbol?) (super-name symbol?) (field-length integer?) (field-ids (list-of symbol?)) (methods method-environment?))) Same as before except methods includes all methods reachable for objects of this class. ------------------------------------------ Q: How to make that happen? Change ------------------------------------------ (deftype roll-up-method-decls (-> (class-decl symbol (list-of symbol)) (list-of method))) (define roll-up-method-decls (lambda (c-decl super-name field-ids) (merge-methods (class-name->methods super-name) (map (lambda (m-decl) (a-method m-decl super-name field-ids)) (class-decl->method-decls c-decl))))) ------------------------------------------ The tricky part is writing merge-methods. Q: what should merge-methods do? has to put methods in order of declaration, from oldest to youngest, but with overriding methods taking the place of the methods they override See figure 5.14 This puts methods in the same slots so that they can be indexed by offsets. (deftype merge-methods (-> ((list-of method) (list-of method)) (list-of method))) (define merge-methods (lambda (super-methods methods) (if (null? super-methods) methods (let ((m-name (method->method-name (car super-methods)))) (if (has-method? m-name methods) (let ((overriding-method (lookup-method m-name methods))) (cons overriding-method (merge-methods (cdr super-methods) (remove-method overriding-method methods)))) (cons (car super-methods) (merge-methods (cdr super-methods) methods))))))) **** methods Q: What searching does this avoid? Don't have to search up the subclass hierarchy in find-method-and-apply. ------------------------------------------ FIND-METHOD-AND-APPLY (deftype find-method-and-apply (-> (symbol symbol object (list-of Expressed-Value)) Expressed-Value)) (define find-method-and-apply (lambda (m-name host-name self args) (let ((declared-methods (class-name->methods host-name))) (if (has-method? m-name declared-methods) (let ((meth (lookup-method m-name declared-methods))) (apply-method meth host-name self args)) (eopl:error 'find-method-and-apply "No method for name ~s" m-name))))) ------------------------------------------ ** variations See the exercises for various things that can be done instanceof, fieldref & fieldset, public/protected/private methods are homework problems *** fields Q: Can classes see fields declared in superclasses? How could we stop that? Would that be a good idea? Q: What's the privacy of fields in this interpreter? They are all protected Q: What we have to do to have static, or class, variables? *** methods Q: How could we implement overloading? See exercises 5.13 and 5.25 *** classes Q: Do we really need classes? prototype languages get by with just objects and extensions of objects, see exercise 5.25. Q: What kind of inheritance does the defined language have? Q: What kinds of problems might be caused by multiple inheritance? *** objects Q: Could we implement tuples of objects? How? Q: Could we define methods on tuples of objects? How?