Princeton University
Computer Science Dept.

Computer Science 441
Programming Languages
Fall 1998

Lecture 14


More on subtyping

Subtleties of mathematical models of subtyping

According to our rules for subtyping records, if
	type point = {x, y: real};
	datatype colortype = Red | Blue | Yellow | Green;
	type colorpoint = {x,y : real; color: colortype}

then Colorpoint <: point

But Colorpoint is not a subset of Point!!

Set of triples not subset of set of pairs.

Two ways of looking at it.

1. Interpret

Point = {p | p is a record with (at least) x and y components which are reals}

Colorpoint = {p | p is a record with (at least) x and y components which are reals, and color component which is colortype}.

Then Colorpoint is a subset of Point.

Fits intuition of subtypes as subsets, but there are technical problems.

If p, q are elts of Point, then how do we determine if p = q?
Is it enough to have p.x = q.x and p.y = q.y?

If so, then green point at origin is equal to red point at origin!

Seem equal as points, but different as colorpoints!

2. Give up intuition of subtypes as subsets and go back to intuition with implicit coercions.

Clear implicit coercion from colorpoint to point - simply forget color component.

Behaves nicely with respect to operations on records.

Can define elements of type point as equivalence classes of records from set point from 1.

Define p =pt q iff p.x = q.x and p.y = q.y

Define p =cpt q iff p.x = q.x, p.y = q.y, and p.color = q.color.

Therefore can have p, q elements of colorpoint s.t. p =pt q but not p =cpt q.

Model built from this given in Bruce & Longo, 1990.

Slightly different model based on definable coercions given by Breazu-Tannen et al in 1991.

Both provide models of language with subtypes and explicit parametric polymorphism.

Object-oriented programming languages

Roots in languages supporting ADT's

Biggest loss in moving from FORTRAN to Pascal is lack of support for modules with persistent local data.

Clu, Ada, and Modula 2 attempted to remedy this by adding clusters, packages, and modules.

In Ada & Modula 2, objects (i.e. packages, and modules) were late additions to an earlier paradigm (Pascal-like)

Called object-based languages.

Goals:

Stepping back a bit further - support development of high-quality software.

Qualities Desired in Software

ADT languages provide reasonable support for all but extensibility (in particular if want minor extensions - but rest is same), some limitations on reuseability.

Object-oriented languages are an attempt to make progress toward these goals.

A programming language is object-oriented if:

  1. It supports objects that are data abstractions (like Modula 2, Ada).

  2. All objects have an associated object type (often called classes). (Bit different from Modula 2, Ada).

  3. Classes may inherit attributes from superclasses. (Very different from Modula 2, Ada)

  4. Computations proceed by sending messages to objects.

  5. Routines may be applied to objects which are variants of those they are designed to be applied to (subtype polymorphism).

  6. Support dynamic method invocation (will be explained later )

Simula 67 first object-oriented language - designed for discrete simulations.

Up until recently, Smalltalk best-known - Alan Kay at Xerox (now at Apple).

Gone through Smalltalk-72,-74,-76,-78,-80.

C++, object-oriented extensions to Pascal, C, LISP, etc.

One of nicest is Eiffel - discuss later (See Meyer's Object-Oriented Software Construction). Also Sather (public-domain variant of Eiffel).

Main idea of object-oriented programming:

Independent objects cooperate to perform a computation by sending messages (requests for action) to each other.

Object-oriented programming:

Object-oriented languages built around following concepts:

Objects are internal data abstractions - only accessible to outer world through associated procedures

Object types

Classes

Most current OOL's identify object types and classes (in particular subtypes and subclasses).

See later this can lead to holes in typing system and/or restrictions in expressibility.

In typical object-oriented programming language, everything is an object.

Abstraction preserved since no object can make changes to the internal state of another object. (Some languages don't enforce.)

- just send messages using methods in public interface.

We will first investigate Java and Eiffel, as examples of object-oriented programming languages and then come back and discuss object-oriented languages in general, especially issues in type systems for object-oriented languages.

Java

Java is an extension of C. Originally known as Oak, it was designed to be a platform independent language for quite simple chips, e.g., those designed for your toaster. Later, as people were getting more frustrated with using C++, its target audience shifted to more general purpose computing, and it gained popularity when it provided support for "Applets", programs that could be embedded in web pages. (Sun's HotJava browser was the first to support applets.)

In my opinion, three factors worked toward the sudden popularity of Java:

  1. Web applications.
  2. Use of C syntax.
  3. Growing frustration with C++.
The reasons that should have contributed to the rise of Java:
  1. Clean and (relatively) simple language design
  2. Use of garbage collection
  3. Built-in support for event-driven programming and concurrency.
Java is not, by any means, a perfect language. The main problems include (in my opinion)
  1. Lack of support for parametric polymorphism.
  2. Weak support for modules.
  3. Overly complex.
  4. Use of C syntax
Guy Steele in invited lecture at OOPSLA '98 last month identified the major problems as the lack of parametric polymorphism and operator overloading. We'll be looking at GJ which is an extension of Java which attempts to remedy the first problem.

Unlike most other OO languages, Java supports "interfaces" for classes, which play a role similar to that of module interfaces for implementations. Interfaces contain only the constants and methods of classes that are intended to be publicly available.

Here is a simple example of an interface:

interface EmployeeSpec{	//  Declares interface implemented by class Employee
   String getName();
   double getWkPay();
}
Any class implementing EmployeeSpec must provide public methods getName() and getWkPay().

Here is a simple example of a class implementing this specification:

public class Employee implements EmployeeSpec{
/** Class to represent an Employee.  Should be abstract class since getWkPay
   isn't really defined here.   */
// fields

   protected String name;      /** name of employee */
   protected Date birthDate;   /** employee's date of birth */

// constructors  -- note that constructors do not have return type!

   public Employee (String emp_name, Date emp_bday){
   /* post: creates Employee object from name and birthdate   */
      name = emp_name;         // initialize fields
      birthDate = emp_bday;
   }

// methods

   public String getName()
   // post: Returns name of Employee
   {
      return name;               // return name field
   }
   
   public int getAge()
   /** post: returns the age of the Employee   */
   {
            // declares today and initializes to today's date
      Date today = new Date();            
            // declare yearDiff and initialize to diff of years
      int yearDiff = (today.getYear() - birthDate.getYear());
      
      if ((today.getMonth() > birthDate.getMonth()) || 
         (today.getMonth() == birthDate.getMonth() && 
                  today.getDay() > birthDate.getDay())){
         return yearDiff;                  // already had birthday this year
      } else{                        
         return yearDiff - 1;         // adjust age if not had birthday yet this year
      }
   }
   
   public double getWkPay()   
   /** post: Return weekly pay.  
      No value assigned for Employees.  Will be overridden in subclasses.   
      Really should be abstract method, but fix later! */
   {
      return 0.0;            
   }
   
   /** This is the main program.  It is written as a method and usually stuck at 
   end of central class of the program.  It is a procedure since it returns type 
   void.  The parameters are values included on the command line durign the actual 
   call of the program.  They must be included, but are not used here.   
   Main is declared to be static because it is only included in the class and not 
   in each object (because it is the main program). */
   public static void main(String args[]){      
         // Create and print my birthday.  Printing implicitly calls toString
      Date kbday = new Date(1948,10,16);                     
      System.out.println("My birthday is "+kbday+".");
      
         // Create new employee
      Employee emp = new Employee("Kim Bruce",kbday);
      
         // Print out info on the employee
      System.out.println("Today, the employee, "+emp.getName()+", is "+emp.getAge()
            +" years old.");
      System.out.println("He makes $"+emp.getWkPay()+" per week!");
   }

}
In the first line of the class, it is indicated that the class implements the interface EmployeeSpec. This means that an instance of the class can be used in any context expecting an element of type EmployeeSpec. In Java, both class name and interface names can be used as the types of variables and parameters.

This class contains 2 instance variables, a constructor (used to create "instances" of the class), 3 methods, and a static routine main which can be used to test the class.

The two instance variables, name and birthDate, are of types String and Date and are "protected". They represent the state of instances of the class.

The "constructor" Employee takes a String and Date and uses them to initialize the instance variables. Constructors are used to create new instances of the class. Constructors always have the same name as the class and never have return types.

The (function) methods getName(), getAge(), and getWkPay() are all public, and represent the operations which instances of the class can perform. Their return types are written in front of the method name, while parameters follow.

The "main" routine is not a method of the class. It can be used as the main program to test the class.

By looking inside main, we can see how to create and use instances of Employee. First, a new Date is created (Date is another class of the system not shown) with the new Date(...) expression and it is assigned to the newly declared variable emp of type Employee. Date is the name of the class, so all of its constructors also have that name. As you will see later, Date has a constructor which takes three ints as parameters.

System.out.print(...) and System.out.println() are used for screen output. The only difference between them is that the second adds a carriage return after it is done printing the string inside. The operator "+" in Java is overloaded and here represents string concatenation. Each object has a built-in method toString(), which is automatically called when it is used in a context expecting a string. Thus writing "+kbday" inside println generates an implicit call of toString which returns a string. Most classes will "override" the definition of toString() in order to make sure the desired information is returned. We'll see how this is done below with WeeklyEmployee.

Next a new employee is created with a call to the constructor of Employee which takes a String and Date as parameters. The following line prints out a string which is constructed by concatenating 5 strings. Two of the strings result from message sends to emp.

Notice that constructor calls always use the keyword new, while message sends are written with the receiver followed by ".", followed by the name of the message and the parameters. There are a number of points in the syntax above that we have not yet mentioned. Each instance variable, constructor, and method is labeled with information on its visibility outside of the class. Public means that it is accessible everywhere. Because the constructor and methods are all labelled public, they can be accessed in main or indeed in any other class. Protected features are only accessible inside methods of the class or extensions of the class. Thus name and birthDate will not be accessible in main. Information hiding in Java is actually more complicated than this. We'll get into more of the details later.

When a new Employee is created, space is allocated for that object which includes slots to hold the instance variables. Different objects of the same class all have the same number of slots for instance variables, but the values of the instance variables are independent between the objects. Thus we can think of a class as a factory or template used for stamping out different objects which all have the same shape. The keyword "static" used in the definition of main indicates that this routine is not associated with individual instances. Instead there is one copy which is just associated with the class itself. This makes sense for main since it is just used to create new instances and test the class. If a variable were declared to be static then there would just be one copy no matter how many instances of the class existed.

The type void has a single value which is normally ignored. Thus a routine whose return type is void is really a procedure. The parameters of main are used to pick up command-line parameters to the execution of the program, just as in C. We probably won't use them here. The above class and interface are part of a collection of files which form a program involving different kinds of classes. Note that the method getWkPay() doesn't really do anything useful. The real reason for defining the class Employee is so that we can collect together some common code which will be used in extensions. Here is an example of such an extension:

/**
   Subclass of Employee for employees paid by the hour.
   Written January 25, 1997, by Kim Bruce.
*/
public class HourlyEmployee extends Employee{
// fields

   protected double hourlySalary;   /** hourly wage of employee */

// constructors

   public HourlyEmployee (String emp_name, Date emp_bday, double hourlyWage)
   /** Construct hourly employee from name, birthday and hourly pay */
   {
      super(emp_name,emp_bday);   // Call constructor from superclass
      hourlySalary = hourlyWage;
   }

// methods

   public void setHourlyPay(double new_salary)
   // post: hourly pay set to new_salary
   {
      hourlySalary = new_salary;
   }
   
   public double getWkPay()
   /** post:  Returns weekly pay.  */  
   // Note this overrides method in Employee
   {
         return (hourlySalary * 40.0);
   }
   
   /** This time we test with an hourly employee   */
   public static void main(String args[]){
         // Create my birthday.
      Date kbday = new Date(1948,10,16);                     
      
         // Create new employee
      HourlyEmployee emp = new HourlyEmployee("Kim Bruce",kbday,5.25);
      
         // Print out info on the employee
      System.out.println("Today, the employee, "+emp.getName()+", is "+emp.getAge()
            +" years old.");
      System.out.println("He makes $"+emp.getWkPay()+" per week!");
   }

}
When one class extends another, we often call the original class the superclass and the extending class a subclass. All of the instance variables and methods of the superclass are implicitly "inherited" in the extension. Thus we do not have to rewrite getName() or getAge() as they are automatically inherited. HourlyEmployee "overrides" the method getWkPay() because we wish to compute the weekly pay based on the hourly salary.

Constructors are NOT inherited (and, in fact, they must have the same name as the class, so couldn't usefully be inherited), so we must include a new constructor, HourlyEmployee. The first line of a constructor of a subclass must be a call of the constructor of the superclass. This is written as super(...).

The new main procedure tests out HourlyEmployee.

I have included on-line several other classes which are part of a system using objects representing several type of employees. They are:

  1. Employee
  2. HourlyEmployee
  3. ExemptEmployee
  4. Date
  5. EmployeeTester
Please look at these to see how they can be used together. Here are a few general points about Java which are relevant to these examples.


CS441 | CS Department | Princeton University