University of Calgary
mail toRob Kremer

Class-Based Languages

Lecture Notes on

Abadi, M. & Cardelli, L. (1996). A Theory of Objects. New York, Springer.  Chapter 2: "Class-Based Languages."

CPSC 701.01 Object Theory


Table of Contents

2.1 Classes and Objects
Class
A class is a description of an object
example

    class cell is
      var
    contents: Integer := 0;
      method get(): Integer is
        return self.
    contents;
      end;
      method
    set(n:Integer) is
        self.
    contents := n;
      end;
    end;
naive storage
model
(we will fix up
this notion
later)

 
Instance
TypeOf

The type of an object created from class c is InstanceTypeOf(c)
  • distinguishes between classes and types (important!)
  • var myCell: InstanceTypeof(cell);
new

Create a new object of type InstanceTypeOf(c) with operator new: new(c)
    var myCell: InstanceTypeof(cell) := new(cell);
dot operator

dot operator is used both for attribute extraction and for method invocation: 
    myCell.contents := n;
    myCell.set(2 * myCell.get());


2.2 Method Lookup
Method Suites
The naive storage model (called embedding) above is not the standard model used: 
  • All objects of the same class have exactly the same methods (but not exactly the same attributes, because the values may be different)
  • Therefore, they can all share the same method table or method suite
  • The attribute records delegate to the method suites for method invocation.
  • This technique is called delegation (as opposed to embedding)
 
Inheritance

When an class has a superclass, method lookup involves searching the direct class's method suite, then chaining to the direct parent's method suite, to its parent's method suite, etc. 

Method suites are organized in a 

  • tree for single inheritance
  • DAG (directed acyclic graph) for multiple inheritance.
Method lookup may be very complex and strategies vary from language to language. 
self

The self reference is always to the object that originated the call (the object that the method would be contained in under the embedded model). 
intended
illusion

Almost all OO languages give the illusion that that methods are embedded. 

But: 

  • methods cannot be extracted from objects
  • methods cannot be updated
    • If they could, method update would change only the objects under embedding, but would change all objects of the class under delegation.
    • Could be unsound (read on... [section 2.6])


2.3 Subclasses and Inheritance
example
Extending the previous example with a subclass (reCell stands for "restorable cell"):: 
    subclass reCell of cell is
      var
    backup:Integer := 0;
      override set(n:Integer) is
        self.
    backup := self.contents;
        super.set(n);
      end;
      method
    restore() is
        self.
    contents := self.backup;
      end;
    end;
The cell class is extended: 
  • adding the attribute backup to save the last value
  • overriding the set() method to save the last value (then call the original set() method to do the job)
  • adding the restore() method
 
Inheritance

Inheritance is sharing the fields (and initial value) and the code of the methods

Informally, when we say reCell inherits from cell, we mean reCell is a subclass of cell, but reCell may override all methods and inherit nothing. 
 

self

self refers to the originating object, so: 
  • methods called from inside methods of an ancestor class may not be the methods of the ancestor class, but may be the methods overridden by some decedent class. 
super

super refers to the immediate parent class. 
  • When a method is called through super, self is still bound to the original caller.
  • There's other ways to access ancestors' methods (e.g. what about languages with multiple inheritance?)
 
Method
Suits
Revisited

  • Method lookup searches up the inheritance chain to find a method
    • cell::set() can't be seen from the reCell object
    • a call to aReCell.get() will invoke aCell:get()
  • Not so with attributes
 
Statically
Typed
Languages

In dynamically typed languages, a full method search has to be done each time. 

In statically typed languages, the type is known at compile time, so the method suite can be collapsed

Multiple
Inheritance

No problem!  Except: 
  • We have to decide what to do about conflicts and duplicates in:
    • attributes
    • methods
    • superclasses
  • What is the meaning of super?
 

 
2.4 Subsumption and Dynamic Dispatch
example
    var myCell: InstanceTypeOf(cell) := new cell;
    var myReCell: InstanceTypeOf(reCell) := new reCell;
    procedure f(x: InstanceTypeOf(cell)) is ... end;

    myCell := myReCell;
    f(myReCell);
  • The last two statements would be illegal in C or Pascal, but they are legal in OO languages.  Why?
 
Subtype
Polymorphism

If c' is a subclass of c, and o':InstanceTypeOf(c'), then o':InstanceTypeOf(c).
 
Subsumption

If a:A and A<:B, then a:B
 
Subclassing-
is-subtyping

InstanceTypeOf(c') <: InstanceTypeOf(c) iff c' is  subclass of c.
 
relation

subsumption ^ subclassing-is-subtyping => subtype polymorphism 
  • Not all languages make the subclassing-is-subtyping assumption (e.g. Java and languages with interfaces)
 
example

    procedure g(x: InstanceTypeOf(cell)) is
      x.set(3);
    end;

    g(myReCell);
 
Static Dispatch

x.set(3) executes cell::set() 
  • based on the compile-time type information
 
Dynamic
Dispatch

x.set(3) executes reCell::set() 
  • based on the run-time value of x (called the true type)
  • dynamic dispatch is used in all OO languages
 
No Run-Time
Effect

Dynamic dispatch implies that subsumption has no run-time effect on objects. 
  • In the example, one cannot "see" reCell::backup and reCell::restore() from inside g() even though its true type is reCell.
  • One can only "see" the attributes and methods of cell
 


2.5 Type Information, Lost and Found
Type Info Lost...
Subsumption hides type information 
  • e.g. the ability to access reCell::backup is lost in the previous example.
 
...and Found

But reCell::backup is not redundant.  It can be accessed when g() calls set()
  • the overriding set() method can access backup (though self).
  • The client still cannot access the hidden information directly.
 
 Back Door

Most languages provide a way to get the type information, for example: 
  • typecase
  • dynamic casting
 
Problems

  • violates object abstraction (reveals private information)
  • introduces a form of dynamic failure
  • makes code less extensible: new subclasses cause typecases and casts to require recoding.
 


2.6 Covariance, Contravariance, and Invariance



AxB is covariant
AxB is read "A cross B" and is the type of the pair <a,b> where a:A and b:B. 

AxB <: A'xB' provided that A<:A' and B<:B' 

Argument for the covariance of Ax B

  • A pair <a,b> with left component a of type A and right component b of type B, has type AxB. 
  • If A <: A' and B <: B', then by subsumption we have a:A' and b:B', so that <a,b> has also type A'xB'.
  • Therefore any pair of type AxB has also type A'xB' whenever A <: A' and B<: B'. 
  • In other words, the inclusion AxB <: A'xB' between product type is valid whenever A<:A' and B <: B'.
 
A->B is 
co/contravariant

A->B is a function with argument of type A and result of type B. 

A->B is contravariant in its left argument (the parameter) and covariant in its right argument (the result). 

A->B <: A'->B' provided that A' <: A and B <: B' 

Argument for the co/contravariance of A->B

  • If B<: B', then a function f of type A->B produces results of type B' by subsumption.
  • If A' <: A, then f accepts also arguments of type A', since these have type A by subsumption.
  • Therefore every function of type A->B has also type A'->B' whenever A' <: A and B <: B'.
  • In other words, the inclusion A->B <: A'->B' between function types is valid whenever A'<:A and B<:B'.
 
A#B is invariant

A#B is a pair where where the components may be updated. 

A#B is invariant on both arguments (neither covariant nor contravariant). 

Argument for the invariance of A#B

  • If A<:A' and B<:B', can we covariantly allow A#B <: A'#B'?
    • If we adopt this inclusion, then from p:A#B we obtain p:A'#B' and we can perform setLft(p,a') for any a':A'.
    • After that, getLft(p) might return an element of type A' that is not an element of type A.  Hence the inclusion A#B <: A#B' is not sound.
  • Conversely, if A'' <: A and B'' <: B, can we contravariantly allow A#B <: A''#B''? 
    • From p:A#B we now obtain p:A''#B'', and we can incorrectly deduce that getLft(p):A''.
    • Hence the inclusion A#B <: A''#B'' is not sound either.
 
Method
arguments are
contravariant

Method arguments are directly analogous to the left argument of A->B. 
  • Thus, overriding methods my generalize the types of the parameters.
  • It doesn't matter if there are multiple parameters, any number of arguments may be taken as a single tuple (which works [covariantly] just like a pair, AxB) argument.
 
Method
return types 
are covariant

Method return types are directly analogous to the right argument of A->B. 
  • Thus, overriding methods may:
    • specialize the return type and 
    • generalize the parameters.
 
Attributes are
invariant

Publicly accessible (updateable) non-method attributes are invariant by analogy to A#B. 
  • Thus, subclasses may not tamper with the types of non-method attributes.
 
Controversy

There has been a lot of controversy about the arguments of methods and results of methods being covariant or contravariant. 
  • e.g. Eiffel still has covariant method arguments.
  • e.g. Visual C++ has invariant method return types
  • It is amazing that there is any argument, given that method co/contravariance is direct consequence of subsumption.
 


2.7 Method Specialization
Method
specialization
on override
Parameters are generalized (contravariantly), result types are specialized (covariantly) as per section 2.6
Method
specialization
on inheritance

When a method is inherited from a class c to a subclass c', self is silently specialized (covariantly!) from InstanceTypeOf(c) to InstanceTypeOf(c'). 

We can think of methods as functions with a hidden first parameter that carries the self reference. 

  • the self argument varies covariantly.
  • any inheritable pre-method of type (InstanceTypeOf(c)xA)->B has by subsumption the type (InstanceTypeOf(c')xA')->B' for any A'<:A and B<:B'. (p.23)
 
Hmmm...

The rules derived for method specialization by inheritance are opposite those for overriding.  Why? 


2.8 Self Type Specialization
Motivation
 It often arises that the return type of a method should vary in the subclass. 

Consider a method that recursively returns its own type: 

    class c is
      var x: Integer := 0;
      method m(): InstanceTypeOf(c) is ... self ... end;
    end;
    class c' of c is
      var y:Integer := 0;
    end;
On inheritance, m() returns InstanceTypeOf(c), as we expect. 
But m() may return self, which would be better (and legally) typed as InstanceTypeOf(c'). 
(But this is not the case in general, because m() may construct a c and return that, which is not a c'.) 
Self as a
return type

We therefore invent a type Self, which is the type of the self reference. 

We can recode c: 

    class c is
      var x: Integer := 0;
      method m(): Self is ... self ... end;
    end;
And we have a more precise typing on inheritance. 
Self as an
argument type

Self can't normally be used as an argument type because that would be a covariant argument. 

But... see section 3.4 and 3.5
 


The University of Calgary
back to previous page
back
Up to page above
up
forward to next page
forward

mail toRob Kremer
Last Modified Aug 15, 2005
CPSC 701.01 Object Theory
 Graduate Course in Software Engineering