10 Classes and Objects

A Class in Oz is a chunk that contains:

10.1 Classes from First Principles

Figure 10.1 shows how a class is constructed from first principles as outlined above. Here we construct a Counter class. It has a single attribute accessed by the atom val. It has a method table, which has three methods accessed through the chunk features browse, init and inc. A method is a procedure that takes a message, always a record, an extra parameter representing the state of the current object, and the object itself known internally as self.


declare Counter
local 
   Attrs = [val]
   MethodTable = m(browse:MyBrowse init:Init inc:Inc)
   proc {Init M S Self}
      init(Value) = M in 
      (S.val) := Value
   end 
   proc {Inc M S Self}
      X inc(Value)=M
   in  
      X = @(S.val) (S.val) := X+Value  
   end 
   proc {MyBrowse M=browse S Self}
      {Browse @(S.val)}
   end 
in 
   Counter = {NewChunk c(methods:MethodTable attrs:Attrs)}
end

Figure 10.1: An Example of Class construction


As we can see, the method init assigns the attribute val the value Value, the method inc increments the attribute val, and the method browse browses the current value of val.

10.2 Objects from First Principles

Figure 10.2 shows a generic procedure that creates an object from a given class. This procedure creates an object state from the attributes of the class. It initializes the attributes of the object, each to a cell (with unbound initial value). We use here the iterator Record.forAll/2 that iterates over all fields of a record. NewObject returns a procedure Object that identifies the object. Notice that the state of the object is visible only within Object. One may say that Object is a procedure that encapsulates the state2.


proc {NewObject Class InitialMethod ?Object}
   State O
in 
   State = {MakeRecord s Class.attrs}
   {Record.forAll State proc {$ A} {NewCell _ A} end}
   proc {O M}
      {Class.methods.{Label M} M State O}
   end 
   {O InitialMethod}
   Object = O
end

Figure 10.2: Object Construction


We can try our program as follows

declare C
{NewObject Counter init(0) C}
{C inc(6)} {C inc(6)}
{C browse}

Try to execute the following statement.

local X in {C inc(X)} X=5 end {C browse}

You will see that nothing happens. The reason is that the object application

{C inc(X)}

suspends inside the procedure Inc/3 that implements method inc. Do you know where exactly? If you on the other hand execute the following statement, things will work as expected.

local X in thread {C inc(X)} end X=5 end  {C browse}

10.3 Objects and Classes for Real

Oz supports object-oriented programming following the methodology outlined above. There is also syntactic support and optimized implementation so that object application (calling a method in objects) is as cheap as procedure calls. The class Counter defined earlier has the syntactic form shown in Figure 10.3:


class Counter 
   attr val
   meth browse 
      {Browse @val}
   end 
   meth inc(Value)
      val := @val + Value
   end 
   meth init(Value)
      val := Value
   end 
end

Figure 10.3: Counter Class


A class X is defined by:

class X ... end

Attributes are defined using the attribute-declaration part before the method-declaration part:

attr A1 ... AN

Then follows the method declarations, each has the form:

meth E S end

where the expression E evaluates to a method head, which is a record whose label is the method name. An attribute A is accessed using the expression @A. It is assigned a value using the statement A := E.

A class can be defined anonymously by:

X = class $ ... end

The following shows how an object is created from a class using the procedure New/3, whose first argument is the class, the second is the initial method, and the result is the object. New/3 is a generic procedure for creating objects from classes.

declare C in 
C = {New Counter init(0)}
{C browse}
{C inc(1)}
local X in thread {C inc(X)} end X=5 end

10.3.1 Static Method Calls

Given a class C and a method head m(...), a method call has the following form:

C, m(...)

A method call invokes the method defined in the class argument. A method call can only be used inside method definitions. This is because a method call takes the current object denoted by self as implicit argument. The method could be defined at the class C or inherited from a super class. Inheritance will be explained shortly.

10.3.2 Classes as Modules

Static method calls have in general the same efficiency as procedure calls. This allows classes to be used as module-specification. This may be advantageous because classes can be built incrementally by inheritance. The program shown in Figure 10.4 shows a possible class acting as a module specification. The class ListC defines some common list-procedures as methods. ListC defines the methods append/3, member/2, length/2, and nrev/2. Notice that a method body is similar to any Oz statement but in addition, method calls are allowed. We also see the first example of inheritance.

class ListC from BaseObject

We also show functional methods, i.e. methods that return results similar to functions. A functional method has in general the following form:

 meth m... $) S E end 

Here the class ListC inherits from the predefined class BaseObject that has only one trivial method: meth noop() skip end.


class ListC from BaseObject 
   meth append(Xs Ys $)
      case Xs
      of nil then Ys
      [] X|Xr then 
         X|(ListC , append(Xr Ys $))
      end 
   end 
   meth member(X L $)
      {Member X L}    % This defined in List.oz
   end 
   meth length(Xs $)
      case Xs
      of nil then 0
      [] _|Xr then 
        (ListC , length(Xr $)) + 1
      end 
   end 
   meth nrev(Xs ?Ys)
      case Xs
      of nil then Ys = nil
      [] X|Xr then Yr in 
         ListC , nrev(Xr Yr)
         ListC , append(Yr [X] Ys)
      end 
   end 
end

Figure 10.4: List Class


To create a module from the module specification one needs to create an object from the class. This is done by:

declare ListM = {New ListC noop}

ListM is an object that acts as a module, i.e. it encapsulates a group of procedures (methods). We can try this module by performing some method calls:

{Browse {ListM append([1 2 3] [4 5] $)}}

{Browse {ListM length([1 2 3]  $)}}

{Browse {ListM nrev([1 2 3]  $)}}

10.4 Inheritance

Classes may inherit from one or several classes appearing after the keyword: from. A class B is a superclass of a class A if:

Inheritance is a way to construct new classes from existing classes. It defines what attributes, features3, and methods are available in the new class. We will restrict our discussion of inheritance to methods. Nonetheless, the same rules apply to features and attributes.

The methods available in a class C (i.e. visible) are defined through a precedence relation on the methods that appear in the class hierarchy. We call this relation the overriding relation:

Now a class hierarchy with the super-class relation can be seen as a directed graph with the class being defined as the root. The edges are directed towards the subclasses. There are two requirements for the inheritance to be valid. First, the inheritance relation is directed and acyclic. So the following is not allowed:

class A from B ... end 
class B from A ... end


Figure 10.5: Illegal class hierarchy


Second, after striking out all overridden methods each remaining method should have a unique label and is defined only in one class in the hierarchy. Hence, class C in the following example is not valid because the two methods labeled m remain.

class A1 meth m(...... end end 
class B1 meth m(...... end end 
class B from B1 end 
class A from A1 end 
class C from A B end


Figure 10.6: Illegal class hierarchy in method m


Also the class C below is invalid, since two methods m is available in C.

class A meth m(...... end end 
class B meth m(...... end end 
class C from A B end

Notice that if you run a program with an invalid hierarchy, the system will not complain until an object is created that tries to access an invalid method. Only at this point of time, you are going to get a runtime exception. The reason is that classes are partially formed at compile time, and are completed by demand, using method caches, at execution time.

10.4.1 Multiple inheritance or Not

My opinion is the following:

10.5 Features

Objects may have features similar to records. Features are stateless components that are specified in the class declaration:

class C from ... 
   feat 
A1 ... AN 
... 
end

As in a record, a feature of an object has an associated field. The field is a logic variable that can be bound to any Oz value (including cells, objects, classes etc.). Features of objects are accessed using the infix '.' operator. The following shows an example using features:

class ApartmentC from BaseObject 
    meth init skip end 
end 
class AptC from ApartmentC 
   feat 
      streetName: york
      streetNumber:100
      wallColor:white
      floorSurface:wood
end

10.5.1 Feature initialization

The example shows how features could be initialized at the time the class is defined. In this case, all instances of the class AptC will have the features of the class, with their corresponding values. Therefore, the following program will display york twice.

declare Apt1 Apt2
Apt1 = {New AptC init}
Apt2 = {New AptC init}
{Browse Apt1.streetName}
{Browse Apt2.streetName}

We may leave a feature uninitialized as in:

class MyAptC1 from ApartmentC 
   feat streetName
end

In this case whenever an instance is created, the field of the feature is assigned a new fresh variable. Therefore, the following program will bind the feature streetName of object Apt3 to the atom kungsgatan, and the corresponding feature of Apt4 to the atom sturegatan.

declare Apt3 Apt4
Apt3 = {New MyAptC1 init}
Apt4 = {New MyAptC1 init}
Apt3.streetName = kungsgatan
Apt4.streetName = sturegatan

One more form of initialization is available. A feature may be initialized in the class declaration to a variable or an Oz-value that has a variable. In the following, the feature is initialized to a tuple with an anonymous variable. In this case, all instances of the class will share the same variable. Consider the following program.

class MyAptC1 from ApartmentC 
   feat streetName:f(_)
end  
 
local Apt1 Apt2 in 
Apt1 = {New MyAptC1 init}
Apt2 = {New MyAptC1 init}
{Browse Apt1.streetName}
{Browse Apt2.streetName}
Apt1.streetName = f(york)

If entered incrementally, will show that the statement

Apt1.streetName = f(york)

binds the corresponding feature of Apt2 to the same value as that of Apt1.

What has been said of features also holds for attributes.

10.6 Parameterized Classes

There are many ways to get your classes more generic, which later may be specialized for specific purposes. The common way to do this in object-oriented programming is to define first an abstract class in which some methods are left unspecified. Later these methods are defined in the subclasses. Suppose you have defined a generic class for sorting where the comparison operator less is needed. This operator depends on what kinds of data are being sorted. Different realizations are needed for integer, rational, or complex numbers, etc. In this case, by subclassing we can specialize the abstract class to a concrete class.

In Oz, we have also another natural method for creating generic classes. Since classes are first-class values, we can instead define a function that takes some type argument(s) and return a class that is specialized for the type(s). In Figure 10.7, the function SortClass is defined that takes a class as its single argument and returns a sorting class specialized for the argument.


fun {SortClass Type}
   class $ from BaseObject 
      meth qsort(Xs Ys)
         case Xs
         of nil then Ys = nil
         [] P|Xr then S L in 
            {self partition(Xr P S L)}
            ListC, append({self qsort(S $)} P|{self qsort(L $)} Ys)
         end 
      end 
      meth partition(Xs P Ss Ls)
         case Xs
         of nil then Ss = nil Ls = nil
         [] X|Xr then Sr Lr in 
            case Type,less(X P $) then 
               Ss = X|Sr Lr = Ls
            else 
               Ss = Sr Ls = X|Lr
            end 
            {self partition(Xr P Sr Lr)}
         end 
      end 
   end 
end

Figure 10.7: Parameterized Classes


We can now define two classes for integers and rationals:

class Int 
   meth less(X Y $)
      X<Y
   end 
end 
class Rat from Object 
   meth less(X Y $)
     '/'(P Q) = X
     '/'(R S) = Y
      in 
     P*< Q*R
   end 
end

Thereafter, we can execute the following statements:

{Browse {{New {SortClass Int} noop} qsort([1 2 5 3 4] $)}}
{Browse {{New {SortClass Rat} noop}
     qsort(['/'(23 3) '/'(34 11) '/'(47 17)] $)}}

10.7 Self Application

The program in Figure 10.7 shows in the method qsort an object application using the keyword self (see below).

meth qsort(Xs Ys)
   case Xs
     ... 
      {self partition(Xr P S L)}
     ... 
   end

We use here the phrase object-application instead of the commonly known phrase message sending because message sending is misleading in a concurrent language like Oz. When we use self instead of a specific object as in

{self partition(Xr P S L)}

We mean that we dynamically pick the method partition that is defined (available) in the current object. Thereafter we apply the object (as a procedure) to the message. This is a form of dynamic binding common in all object-oriented languages.

10.8 Attributes

We have touched before on the notion of attributes. Attributes are the carriers of state in objects. Attributes are declared similar to features, but using the keyword attr instead. When an object is created each attribute is assigned a new cell as its value. These cells are initialized very much the same way as features. The difference lies in the fact that attributes are cells that can be assigned, reassigned and accessed at will. However, attributes are private to their objects. The only way to manipulate an attribute from outside an object is to force the class designer to write a method that manipulates the attribute. In the Figure 10.8 we define the class Point. Note that the attributes x and y are initialized to zero before the initial message is applied. The method move uses self-application internally.


class Point from BaseObject 
   attr x:0 y:0
   meth init(X Y)
      x := X
      y := Y             % attribute update
   end 
   meth location(L)
      L = l(x:@x y:@y)     % attribute access
   end 
   meth moveHorizontal(X)
      x := X
   end 
   meth moveVertical(Y)
      y := Y
   end 
   meth move(X Y)
      {self moveHorizontal(X)}
      {self moveVertical(Y)}
   end 
   meth display 
    % Switch the browser to virtual string mode
     {Browse "point at ("#@x#" , "#@y#")\n"}  
   end 
end

Figure 10.8: The class Point


Try to create an instance of Point and apply some few messages:

declare P
P = {New Point init(2 0)}
{P display}
{P move(3 2)}

10.9 Private and Protected Methods

Methods may be labeled by variables instead of literals. These methods are private to the class in which they are defined, as in:

class C from ... 
  meth A(X) ... end 
  meth a(...) {self A(5)} ... end 
  .... 
end

The method A is visible only within the class C. In fact the notation above is just an abbreviation of the following expanded definition:

local A = {NewName} in 
   class C from ... 
      meth !A(X) ... end 
      meth a(...) {self A(5)} ... end 
      ... 
   end 
end

A is bound to a new name in the lexical scope of the class definition.

Some object-oriented languages have also the notion of protected methods. A method is protected if it is accessible only in the class it is defined or in descendant classes, i.e. subclasses and subsubclasses etc. In Oz there is no direct way to define a method to be protected. However there is a programming technique that gives the same effect. We know that attributes are only visible inside a class or to descendants of a class by inheritance. We may make a method protected by first making it private and second by storing it in an attribute. Consider the following example:

class C from ... 
  attr pa:A
  meth A(X) ... end 
  meth a(...) {self A(5)} ... end 
  ... 
end

Now, we create a subclass C1 of C and access method A as follows:

class C1 from C 
  meth b(...) L=@pa in {self L(5)} ... end 
  ... 
end

Method b accesses method A through the attribute pa.

Let us continue our simple example in Figure 10.8 by defining a specialization of the class that in addition of being a point, it stores a history of the previous movement. This is shown in Figure 10.9.


class HistoryPoint from Point 
   attr 
      history: nil
      displayHistory: DisplayHistory
   meth init(X Y)
      Point,init(X Y)  % call your super
      history := [l(X Y)]
   end 
   meth move(X Y)
      Point,move(X Y)
      history := l(X Y)|@history
   end 
   meth display 
      Point,display
      {self DisplayHistory}
   end 
   meth DisplayHistory  % made protected method
      {Browse "with location history: "}
      {Browse @history}
   end 
end

Figure 10.9: The class History Point


There are a number of remarks on the class definition HistoryPoint. First observe the typical pattern of method refinement. The method move specializes that of class Point. It first calls the super method, and then does what is specific to being a HistoryPoint class. Second, DisplayHistory method is made private to the class. Moreover it is made available for subclasses, i.e. protected, by storing it in the attribute displayHistory. You can now try the class by the following statements:

declare P
P = {New HistoryPoint init(2 0)}
{P display}
{P move(3 2)}

10.10 Default Argument Values

A method head may have default argument values. Consider the following example.

meth m(X Y d1:Z<=0 d2:W<=0) ... end

A call of the method m may leave the arguments of features d1 and d2 unspecified. In this case these arguments will assume the value zero.

We continue our Point example by specializing Point in a different direction. We define the class BoundedPoint as a point that moves in a constrained rectangular area. Any attempt to move such a point outside the area will be ignored. The class is shown in Figure 10.10. Notice that the method init has two default arguments that give a default area if not specified in the initialization of a new instance of BoundedPoint.


class BoundedPoint from Point 
   attr 
      xbounds: 0#0
      ybounds: 0#0
      boundConstraint: BoundConstraint
   meth init(X Y xbounds:XB <= 0#10 ybounds:YB <= 0#10)
      Point,init(X Y) % call your super
      xbounds := XB
      ybounds := YB
   end 
   meth move(X Y)
      if {self BoundConstraint(X Y $)} then 
        Point,move(X Y)
      end 
   end 
   meth BoundConstraint(X Y $)
      (X >= @xbounds.andthen 
       X =< @xbounds.andthen 
       Y >= @ybounds.andthen 
       Y =< @ybounds.2 )
   end 
   meth display 
      Point,display
      {self DisplayBounds}
   end 
   meth DisplayBounds 
      X0#X1 = @xbounds
      Y0#Y1 = @ybounds
      S = "xbounds=("#X0#","#X1#"),ybounds=(" 
          #Y0#","#Y1#")" 
   in 
      {Browse S}
   end 
end

Figure 10.10: The class BoundedPoint


We conclude this section by finishing our example in a way that shows the multiple inheritance problem. We would like now a specialization of both HistoryPoint and BoundedPoint as a bounded-history point. A point that keeps track of the history and moves in a constrained area. We do this by defining the class BHPoint that inherits from the two previously defined classes. Since they both share the class Point, which contains stateful attributes, we encounter the implementation-sharing problem. We, any way, anticipated this problem and therefore created two protected methods stored in boundConstraint and displayHistory to avoid repeating the same actions. In any case, we have to refine the methods init, move and display since they occur in the two sibling classes. The solution is shown in Figure 10.11. Notice how we use the protected methods. We did not care avoiding the repetition of initializing the attributes x and y since it does not make any harm. Try the following example:

declare P
P = {New BHPoint init(2 0)}
{P display}
{P move(1 2)}

This pretty much covers most of the object system. What is left is how to deal with concurrent threads sharing a common space of objects.


class BHPoint from HistoryPoint BoundedPoint 
   meth init(X Y xbounds:XB <= 0#10 ybounds:YB <= 0#10)
    % repeats init
      HistoryPoint,init(X Y)
      BoundedPoint,init(X Y xbounds:XB ybounds:YB)  
   end 
   meth move(X Y)
      L = @boundConstraint in 
      if {self L(X Y $)} then 
        HistoryPoint,move(X Y)
      end 
   end 
   meth display 
      BoundedPoint,display
      {self @displayHistory}
   end 
end

Figure 10.11: The class BHPoint



1. In fact, classes may have some invisible state. In the current implementation, a class usually has method cache, which is stateful
2. This is a simplification; an object in Oz is a chunk that has the above procedure in one of its fields; other fields contain the object features
3. To be defined shortly

Seif Haridi and Nils Franzén
Version 1.4.0 (20080702)