CSE130 LECTURE NOTES


November 8, 1999
 
 

ADMINISTRATION

Reminder: The midterm will be on Wednesday this week, November 10.
 
 

IMPERATIVE PROGRAMMING REVISITED

Today we will look at object-oriented programming from the point of view of trying to understand it in terms of programming language concepts that we know already.  We will compare OOP to abstract data types.  However first we will look again at imperative versus functional programming.

Updatable variables and assignment commands are the essence of imperative programming.  There are two basic reasons for using variables: (1) to remember intermediate results (2) to keep track of things changing in the outside world.

The second use of updatable variables is much more fundamental.  If you think of a software module as simulating something in the
real world, e.g. a database system simulates the population of UCSD students, then it is very natural to update variables as events
happen in the real world, e.g. a student adds a class.

In object-oriented programming, an object is a generalization of a variable as used to model something in the outside world that can change.
 
 

USING VARIABLES TO REMEMBER TEMPORARY RESULTS CAN BE AVOIDED

Consider this version of the factorial function:
function fact (n: integer): integer;
    var temp: integer;
begin
    if n = 0 then temp := 1
    else
        begin
            temp := n-1;
            temp := fact(temp);
            temp := n*temp
        end;
    return temp
end;
Is using updatable variables to remember intermediate results really necessary? The answer is no, because using nested
expressions, the programmer can let the compiler take care of computing intermediate results in the right order:
function fact (n: integer): integer;
begin
    return ifthenelse( n=0, 1, n*fact(n-1) )
end;
Temporary variables are inessential.
 
 

WHAT IS AN OBJECT?

Remember the second, most important, use for updatable variables: to keep track of things changing in the outside world.

If you think of a software module as simulating something in the real world, e.g. a database system simulates the population of UCSD students, then it is very natural to update variables as events happen in the real world, e.g. a student adds a class.

In object-oriented programming, an object is a generalization of a variable as used to model an entity in the outside world.

Definition: An object is a variable with implementation-independence.

The idea of "representation-independence" is a special case of "implementation-independence", which is an idea that can apply to types, to variables, constants, functions, procedures, and more.

In modeling a real-world entity, what is important is that the model should behave like the entity being modelled, not the details of how the model is implemented.

An object contains data, called its state or private variables, and it has operations that can update its private variables called methods.  Methods are sometimes called member functions.

A class is a type of objects. We say that an object is an instance of a class.  The operations of an object typically have side-effects, so they are not pure functions.

Objects are thought of as active individuals rather than passive values.  Invoking an operation of an object is called sending a message.

Therefore, instead of writing x := plus(x,y) we write send x "add y" for example.  This calls the add method of the object named x. Every method has an unwritten (implicit) first argument, which is the object itself.
 
 

CLASSES COMPARED TO ABSTRACT DATA TYPES

The definition of a class looks similar to the implementation of an ADT, but no separate signature or specification is given.
class financial-history =
begin
    state cash, receipts, expenses
    method init = cash := 0; receipts := []; expenses := [];
    method receive (amount) = receipts := amount :: receipts; cash := cash + amount;
    method spend (amount) = expenses := amount :: expenses; cash := cash - amount;
    method cash? () = return cash;
end;
Individual objects can be declared in the same way as variables:
var myaccount: financial-history;
A class introduces a collection of operations which are imperative.  Methods modify objects which have an internal, updatable state.

In contrast, an ADT introduces a new type. Any operation on a value of the new type must take the value as an input parameter.  With a pure ADT, there are no operations that modify values of the type.  Instead, some operations can generate fresh, new values of the type.

 

RESTRICTED IMPERATIVE UPDATING

A type is pure if variables having the type cannot be updated in any way, i.e. no assignment commands are available for this type.  In functional programming, all types are pure.

Errors are very likely with unlimited direct updating of composite variables.  Consider what happens with the following procedure
if the user gives an address with less than 5 lines:

const length = 5;
type address = array [1..length] of string;

procedure update (a: address);
    var j: integer := 1; s: string;
begin
    while (j <= length) and not eof(input) do
    begin
        readln(s);
        a[j] := s;
        j := j+1
    end
end;

Classes are typically not pure, because objects can be updated, i.e. modified.  However programming with objects is still safer than programming with regular composite types, because updating can only be done through predefined methods.
 
 

INHERITANCE

From a programming language (as opposed to programming methodology) point of view, the major innovation in OOP is inheritance.  Here is an example of what is called a derived class definition:
class tax-history parent financial-history =
begin
    state deductions;
    method init = deductions := [];
    method deductible-spend (amount) =
        deductions := append(amount,deductions);
        send self spend(amount);
end;
Every object of type tax-history has three financial-history state variables, plus one new state variable. It can respond to all financial-history messages, and some additional messages.
 
 

DYNAMIC BINDING

With inheritance there is a problem concerning how methods are bound to method-names.

One method of an object can call another of the same object if necessary. The name self refers to the current object, which is the implicit first argument of each method.

Calls to a self-method should refer to the method with the given name in the child class. This allows child class methods to override parent methods.

For example, sending the message id to an object of class D with the following class definitions should give I'm a type D object:

class C =
begin
    method whoami = return "I'm a type C object";
    method id = print (send self whoami)
end;

class D =
begin
    method whoami = return "I'm a type D object";
end;

Under standard static scoping, the name whoami in the body of the method C.id would refer to the method C.whoami. This is not what we want.

Method names should follow dynamic binding: the method corresponding to a name should be found in the run-time environment, not in the compile-time environment.
 
 



Copyright (c) by Charles Elkan, 1999.