OBJECT-ORIENTED PL/I

Discussion paper by Peter Elderon.

From comp.lang.pl1 Sat Nov 23 14:13:20 1996
From: Dave Jones 
Newsgroups: comp.lang.pl1
Subject: Ptere Elderon's OO-PL/I presentation.
Date: Mon, 18 Nov 1996 13:51:04 -0600
Organization: NeoSoft, Inc.  

INTRODUCTION

A class consist of objects and the operations (also known as methods) that may be performed on such objects. Classes of- fer the following features: In some object-oriented languages, the cost of method invo- cation is kept to a minimum but at the expense of what is sometimes called "fragile superclasses": changes to a class require all users and subclasses of that class to be recom- piled.

In PL/I, class users and subclasses need not be recompiled if

  1. 1. new methods are added to a class
  2. 2. new instance variables are added to a class
This is what you would expect from a math library, for in- stance.

PL/I also clearly separates the client's view of a class from the implementer's by defining a class via:

  1. 1. a statement that defines the client's view:
  2. 2. a package that defines the implementation:

THE CLIENT'S VIEW OF A CLASS

The statement that defines the client's view of a class is the DEFINE CLASS statement, which would typically be in an include file. It has the syntax:
 
      DEFINE
        CLASS
          
          INHERITS (  )
          METHODS (  ) ;
 
The INHERITS clause names this class's parent. Every PL/I class is a subclass of the PL/I-provided Object class (ex- cept, of course, the Object class itself). Note also that a class may have only one parent; like Smalltalk and Java, object-oriented PL/I supports only single inheritance.

The METHODS clause names the operations permitted on objects of this class. These methods are the only way a client can manipulate objects of this class. There is no public or pro- tected data: all data is encapsulated by methods.

For example, the following statement defines a "student" class

 
      define
        class
          student
          inherits( object )
          methods
            (
              setUp entry( char(64) varying,
                            char(6) )
 
             ,get_Id entry() returns( char(6) )
 
             ,get_Name entry() returns( char(64) varying )
 
             ,print entry()
            );
 

CLASS INSTANCES

The DEFINE CLASS statement shares some important similari- ties with the DEFINE STRUCTURE statement.

The name appearing in the specification of a HANDLE attri- bute may be the name specified in a DEFINE STRUCTURE state- ment or in a DEFINE CLASS statement. A variable declared as a HANDLE for a class is also known as an instance of that class.

Just as the only access to the elements in a defined struc- ture is via the => operator applied to a handle to that structure, the only access to the methods in a defined class is is via the => operator applied to a handle to that class.

For instance, given the student class in the example above, the following statements declare an instance of that class and invoke the setup method for that class.

 
       dcl  s  handle student;
 
       call s=>setUp( 'Z. Dobson', '030507' );
 

The example above is incomplete since before you apply a method to an object, the object must be instantiated. The primary way to instantiate an object is via the NEW type function applied to the name of that class. So the example above could be completed to look like:

 
       dcl  s  handle student;
 
       s = new(: student :);
       call s=>setUp( 'Z. Dobson', '030507' );
 
So just as for defined structures, storage for defined classes is obtained via the new type function.

If an instance of a class is obtained via the new function, it should be destroyed by applying to it the delete method from the PL/I object class. So the student instance ob- tained above could be destroyed via

 
       call s=>delete();
 

THE IMPLEMENTER'S VIEW OF A CLASS

A PL/I package implements a class by specifying the name of the class in the OWNS clause of the PACKAGE statement. For instance, the following statement indicates that this pack- age implements the student class:

student: package owns( student );

A package that owns a class must contain a DEFINE CLASS statement for that class.

A package that owns a class must also define any private data for a class via the DEFINE INSTANCE statement:

 
      DEFINE
        INSTANCE
          
          (  ) ;
The DEFINE INSTANCE statement is valid only inside a package that owns the class whose data that it defines.

The declaration commalist in the define instance statement must specify must specify either a scalar or a structure or an array thereof.

QUESTION: should a list of scalars and/or structures be allowed??

A package that owns a class must implement all the methods in that class by containing a level-one procedure for each method defined by the class. Note that these procedures are not exported from the package; they cannot be invoked from outside or inside the package except via a handle.

As indicated earlier, PL/I allows new methods and variables to be added to a parent class without "damaging" any of is subclasses or clients. This implies that classes must be created at run-time. The compiler generates all the code to do this. The actual creation of the class occurs in the PL/I package that owns the class.

The code generated by the compiler produces several external names built using the name of the class:

Each method implemented by a class receives one undeclared byvalue parameter, which can be referenced by the built-in function SELF. SELF is a handle for the class being imple- mented and can be used to access instance data as well as to invoke methods. For instance, the following code implements the student class:
 
      student:
        package
        owns( student );
 
        %include class;
        %include student;
 
        dcl self builtin;
 
        define
          instance
            student
             ( 1 student_data
                ,2 student_id      char(6)
                ,2 student_name    char(64) varying
             )
          ;
 
        setUp: proc( name, id );
 
          dcl name char(64) varying;
          dcl id   char(6);
 
          self=>student_id = id;
          self=>student_name = name;
        end;
 
        get_Id: proc returns( char(6) );
 
          return( self=>student_id );
        end;
 
        get_Name: proc returns( char(64) varying );
 
          return( self=>student_name );
        end;
 
        print: proc;
 
          put skip edit( ' id', ': ',
                         self=>student_id ) (a(12),a,a );
          put skip edit( ' name', ': ',
                         self=>student_name ) (a(12),a,a );
        end;
 
      end student;
A package that owns a class may also override methods in its parents' classes. The DEFINE OVERRIDES statement indicates which parent methods a class will override:
 
      DEFINE
        OVERRIDES
          
          (  ) ;
 
The DEFINE OVERRIDES statement is valid only inside a pack- age that owns the class named in it. This package must also contain a level-one procedure for each method overridden by the class.

A method may often want to override a parent method in order to perform some special processing before or after the normal processing performed by the parent method. In order to be able to do this, it must be possible to specify that the method that should be applied to self is that defined by its parent. The PARENT built-in function provides this ability.

For instance, if a subclass of the student class overrode the student print method because it wanted to print addi- tional data belonging to the subclass, the first thing the subclass print method might want to do is invoke the student print method as in the following example:

 
       print: proc;
         call parent=>print();
         /* use self to print additional data */
       end;

THE PL/I OBJECT CLASS

Every class has the object class as its root class. The ob- ject class defines a set of common methods that can be ap- plied to all class instances.

The object class define statement is

 
          define
            class
              object
              methods
                (
                   delete entry()
                   /* used to destroy an instance of a class   */
                   /* it will invoke the uninit method         */
 
                ,  init entry()
                   /* invoked when an instance is created      */
                   /* the class object version does nothing    */
                   /* but exist in order to be overridden      */
 
                ,  uninit entry()
                   /* invoked when an instance is destroyed    */
                   /* the class object version does nothing    */
                   /* but exist in order to be overridden      */
 
                );

CONFORMANCE

Only a handle may be assigned only to another handle But the source in such an assignment should be a handle of the target handle's class or one of its subclasses

For example, given the student class with two derived classes, undergrad and graduate, consider

 
           dcl s handle student;
           dcl u handle undergrad;
           dcl g handle graduate;
 
           ...
           s = u;  /* valid */
           u = s;  /* valid */
 
           ...
           s = g;  /* valid */
           u = s;  /* invalid */
 
A new condition CONFORMANCE, which can be disabled or enabled like SUBSCRIPTRANGE, will be raised when enabled if such an invalid assignment is attempted.

CONSTRUCTORS

QUESTION: Should constructors be supported??

In examples used above, the student class has a method, setUp, that can be used to initialize its instance data. However, the student class could be defined with a constructor that would be invoked to perform this initialization.

If a method list for a class contains a method with the same name as the class name, that method is called a constructor. The constructor will be invoked when the new function is invoked, and if the constructor requires parameters, the new function must be invoked with the necessary additional arguments. For instance, the student class above could be defined via

 
      define
        class
          student
          inherits( object )
          methods
            (
              student entry( char(64) varying,
                             char(6) )
 
             ,get_Id entry() returns( char(6) )
 
             ,get_Name entry() returns( char(64) varying )
 
             ,print entry()
            );
 
The student class would then be instantiated via
 
      dcl s handle student;
      s = new(: student, 'Z. Dobson', '030507' :);
Constructor methods cannot be invoked directly except a constructor method itself may invoke via the parent builtin the constructor for its parent class.

AN EXAMPLE WITH INHERITANCE AND POLYMORPHISM

Using the student class above, two different subclasses could be defined for it via the statements:
 
      define
        class
          undergrad
          inherits( student )
          methods
            (
              setUp_Undergrad entry( char(64) varying,
                                     char(6),
                                     char(8) )
            );
 
      define
        class
          graduate
          inherits( student )
          methods
            (
              setUp_Grad entry( char(64) varying,
                                char(6),
                                char(8) )
            );
 
The implementation of the undergrad class could be
 
 
      undergrad: package owns( undergrad );
 
        %include class;
        %include student;
        %include undergrad;
 
        dcl self builtin;
        dcl parent builtin;
 
        define
          instance
            undergrad
             (
                underGradDate char(8)
             )
          ;
 
        define
          overrides
            undergrad
             (
                print
             )
          ;
 
        setUp_Undergrad: proc( name, id, graddate );
 
          dcl name char(64) varying;
          dcl id   char(6);
          dcl gradDate char(8);
 
          call self=>setup( name, id );
          self=>UnderGradDate = graddate;
        end;
 
        print: proc;
 
          call parent=>print;
          put skip edit( ' grad date', ': ', self=>undergradDate )
                       ( a(12), a, a );
        end;
 
      end undergrad;
While the implementation of the graduate class could be
 
      graduate: package owns(graduate);
 
        %include class;
        %include student;
        %include graduate;
 
        dcl self builtin;
        dcl parent builtin;
 
        define
          instance
            graduate
             (
                gradDegree char(8)
             )
          ;
 
        define
          overrides
            graduate
             (
                print
             )
          ;
 
        setup_Grad: proc( name, id, degree );
 
          dcl name char(64) varying;
          dcl id   char(6);
          dcl degree char(8);
 
          call self=>setup( name, id );
          self=>GradDegree = degree;
        end;
 
        print: proc;
 
          call parent=>print;
          put skip edit( ' degree', ': ', self=>gradDegree )
                       ( a(12), a, a );
        end;
 
      end graduate;

The following code is client code using these classes. It obtains an instance of the undergrad class, initializes it and saves the handle for that instance in an array of stu- dent handles. It then obtains an instance of the graduate class, initializes it and saves the handle for that instance in the same array of student handles. Finally it loops through that array and prints the data for each student. The print function is polymorphic since the two subclasses have

each overridden it: when applied to a student handle it will call the print method for the undergrad or graduate class in accordance with the class to which the handle actually points.

So the output of the following program would be, thanks to polymorphism:

 
 
         id          : 030507
         name        : Z. Dobson
         grad data   : 19550611
 
         id          : 111317
         name        : L. Jim
         degree      : Ph. D.
 
 
      registrar: proc;
 
        %include class;
        %include student;
        %include undergrad;
        %include graduate;
 
        dcl s(20) handle student;
        dcl u     handle undergrad;
        dcl g     handle graduate;
        dcl c     fixed bin(31) init(0);
        dcl jx    fixed bin(31);
 
        /* add a sample undergrad student */
        c = c + 1;
        u = new(: undergrad :);
        call u=>setUp_Undergrad( 'Z. Dobson', '030507', '19550611' );
        s(c) = u;
 
        /* add a sample graduate student */
        c = c + 1;
        g = new(: graduate :);
        call g=>setUp_Graduate( 'L. Jim', '111317', 'Ph. D.' );
        s(c) = g;
 
        /* print out all the students */
        do jx = 1 to c;
          call s(jx)=>print();
          put skip;
        end;
      end;