Sather - A Language Manual - Code Inclusion and Partial Classes

[back] [Abstract] [Copyright Notice] [Contents] [next]

# Sather - A Language Manual - Chapter 4
Code Inclusion and Partial Classes

Object oriented languages usually support the derivation of new classes by inheriting from existing classes and modifying them. In Sather, the notion of inheritance is split into two separate concepts - type relations between classes and code relations between classes. In this chapter we will deal with the latter (and simpler) concept, that of reusing the code of one class in another. We refer to this as implementation inheritance or code inclusion.

## 4.1 Include Clauses

The re-use of code from one class in another class is defined by include clauses. These cause the incorporation of the implementation of the specified class, possibly undefining or renaming features with feature modifier clauses. The `include` clause may begin with the keyword '`private`', in which case any unmodified included feature is made private.

    include A a->b, c->, d->private d;
    private include D e->readonly f;

Code inclusion permits the re-use of code from a parent concrete class in child concrete class . Including code is a purely syntactic operation in Sather. To help illustrate the following examples, we repeat the interface of `EMPLOYEE`.

    class EMPLOYEE is
       private attr wage:INT;
       readonly attr name:STR;
       attr id:INT;
       const high_salary:INT := 40000;

       create(a_name:STR, a_id:INT, a_wage:INT):SAME is ...

       highly_paid:BOOL is ...
    end;

Routines that are redefined in the child class over-ride the corresponding routines in the included class. For instance suppose we define a new kind of `EMPLOYEE` - a `MANAGER`, who has a number of subordinates.

    class MANAGER is
       include EMPLOYEE
          create->private oldcreate;
       -- Include employee code and rename create to 'oldcreate'

       readonly attr numsubordinates:INT; -- Public attribute

       create(aname:STR, aid:INT,awage:INT,nsubs:INT):SAME is
          -- Create a new manager with the name 'aname'
          -- and the id 'aid' and number of subordinates = 'nsubs'
          res ::= oldcreate(aname,aid,awage);
          res.numsubordinates := nsubs;
          return res;
       end;
    end;

See A Running Example: Employees, section 2.9 for the EMPLOYEE definition. The create routine of the `MANAGER` class extends the `EMPLOYEE` create routine, which has been renamed to oldcreate (renaming is explained below) and is called by the new create routine.

Points to Note

- The order of inclusion is not significant and cannot affect conflicts.

- External classes may be included if the interface to the language permits this; external Fortran and C classes may not be included.

- Immutable (see Immutable Classes, chapter 8) and reference classes cannot be mixed into a single class during inclusion. In the case of arrays (see Support for Arrays, section 6.2), there cannot be include paths from reference types to `AVAL` or from immutable types to `AREF` i.e. reference types cannot include an immutable array portion and immutable classes cannot include a reference array portion.

- There must be no cycle of classes such that each class includes the next, ignoring the values of any type parameters.

      class A is include B; ...
      class B is include C; ...
      class C is include A; ...

- If `SAME` occurs in included code it is interpreted as the eventual type of the class (as late as possible). We make use of this fact every time we include a create routine that returns `SAME`

      class  FOO is
         create:SAME is  return new; end;
      end;

      class SON_OF_FOO is
         include FOO;
         -- Since create returns SAME, we have create:SON_OF_FOO;
      end;

      class GRANDSON_OF_FOO is
         include SON_OF_FOO;   -- Now we have create:GRANDSON_OF_FOO;
      end;

      a ::= #GRANDSON_OF_FOO; -- Calls GRANDSON_OF_FOO::create:SAME,
             -- which returns a GRANDSON_OF_FOO.

### 4.1.1 Renaming

The include clause may selectively rename some of the included features. It is also possible to include a class and make all routines private, or some selectively public

    class MANAGER is
       private include EMPLOYEE;
       -- All included features are made private

    class MANAGER is
       private include EMPLOYEE  id->id;
       -- Makes the "id" routine public and others stay private

If no clause follows the '`->`' symbol, then the named features are not included in the class. This is equivalent to 'undefining' the routine or attribute.

    class MANAGER is
       include EMPLOYEE id->;  -- Undefine the "id" routine
       attr id:MANAGER_ID;     -- This ' id' has a different type

Points to note

- All overloaded features must be renamed at the same time - there is no way to specify them individually.

- A public routine can be made private by either a private include or by renaming the individual routine to be private

      class MANAGER is
         include EMPLOYEE id->private id;
         -- Renames both reader and writer routines of the attribute 'id'

- In a private include, renaming a particular feature has the effect of making just that one feature public. For instance

      class MANAGER is
         private include EMPLOYEE
            name->name;           -- only 'name' is made public

- Iterator names may only be renamed as iterator names.

- It is an error if there are no appropriate methods to rename in the included class.

- Both a reader and a writer method must exist if '`readonly`' is used in a renaming clause.

      class I_INTERVAL is
         private attr first, size:INT;

         finish:INT is
            return first + size - 1;
         end;

         finish(fin:INT) is
            size := fin - first + 1;
         end;
      ...

      class LINE_SEGMENT is
         include I_INTERVAL
            finish->readonly finish;
         -- makes private finish(fin:INT)
         -- and leaves public finish:INT;

### 4.1.2 Multiple Inclusion

Sather permits inclusion of code from multiple source classes. The order of inclusion does not matter, but all conflicts between classes must be resolved by renaming. The example below shows a common idiom that is used in create routines to permit an including class to call the attribute initialization routines (by convention, this is frequently called 'init') of parent classes.

    class PARENT1 is
       attr a:INT;

       create:SAME is
          return new.init;
       end;

       private init:SAME is
          a := 42;
          return self;
       end;
    end;

In the above class, the attributes are initialized in the `init` routine. The use of such initialization routines is a good practice to avoid the problem of assigning attriutes to the "self" object in the create routine (which is void)

The other parent is similarly defined

    class PARENT2 is
       attr c:INT;

       create:SAME is
          return new.init;
       end;

       private init:SAME is
          c := 72;
       end;
    end;

In the child class, both parents are initialized by calling the initialization routines in the included classes

    class DERIVED is
       include PARENT1 init-> PARENT_init;
       include PARENT2 init-> PARENT2_init; -- Rename init

       attr b:INT;

       create:SAME is
           -- a gets the value 42, b the value 99 and c the value 72
          return new.PARENT1_init.PARENT2_init.init
       end;

       private init:SAME is b := 99; return self;
    end; -- class DERIVED

### 4.1.3 Resolving conflicts

Two methods which are included from different classes may not be able to coexist in the same interface. They are said to conflict with each other. For a full discussion of resolving conflicts, please see Conflicts during code inclusion, subsection 5.7.5. We have to first present the general overloading rule, before discussing when included signatures will conflict and what can then be done about it.

For now, we simply note that if we have signatures with the same name in two included classes, we can simply rename one of them away i.e.

    class FOO is
       include BAR bar->;     -- eliminate this 'bar' routine
       include BAR2;          -- Use the 'bar' routine from BAR2

## 4.2 Partial Classes and Stub routines

Partial classes have no associated type and contain code that may only be included by other classes. Partial classes may not be instantiated: no routine calls from another class into a partial class are allowed, and no variables may be declared in another class of such a type.

A stub feature may only be present in a partial class. They have no body and are used to reserve a signature for redefinition by an including class. If code in a partial class contains calls to an unimplemented method, that method must be explicitly provided as a stub. The following class is a stub debugging class which checks on the value of a boolean and then prints out a debugging message (preceeding by the class name of 'self')

    partial class DEBUG_MSG is
       stub debug:BOOL;

       debug_msg(msg:STR) is
       -- Prints out the type of "self" and a debugging message
          if not debug then
             -- Don't print out the message if the debug flag is false
             return;
          end;
          type_str:STR;
          -- Declared here since used in both branches of the if
          if ~void(self) then
             type_id:INT := SYS::tp(self);
             -- SYS::tp will not work if self is void!
             type_str:STR := SYS::str_for_tp(type_id);
          else
             type_str := "VOID SELF";
          end;
          #OUT + "Debug in class:" + type_str + " " + msg + "\n";
       end;
    end;

This class can be used by some other class - for instance, a main routine that wants to print out all the arguments to main. The stub routine 'debug' must be filled in using either an attribute (a constant, in this case) or a routine.

    class MAIN is
       include DEBUG_MSG;

       const debug:BOOL := true;           -- Fill in the stub.

       main(args:ARRAY{STR}) is
          loop
             arg:STR := args.elt!
             debug_msg("Argument:"+arg);    -- Print out the argument
          end;
       end;
    end;

Points to note

- Partial classes cannot be used to instantiate parameters of a parametrized class. For example, 'ARRAY{DEBUG_MSG}' would not be legal.

- Create cannot be called on a partial class, nor can a partial class occur as the type of a variable or attribute.

### 4.2.1 Mixins: A Prompt Example

This code demonstrates the use of partial classes. Each `MIXIN` class provides a different way of prompting the user; each can be combined with `COMPUTE` to make a complete program. The stub in `COMPUTE` allows that class to be type checked without needing either mix-in class. Only `COMPUTE_A` and `COMPUTE_B` may actually be instantiated.

Now suppose that we have a 'COMPUTE' class that requires a prompt for some input data. It can leave the prompt routine as a stub, which will later be filled in by some prompt class

    partial class COMPUTE is
       stub prompt_user:STR;

       main is
          res ::= prompt_user;
          -- Convert it to an integer and do something with it
          i:INT := res.cursor.get_int;
          #OUT + "I'm going to compute with this number, now:" + i + "\n";
          ...
       end;
    end; -- partial class COMPUTE

We can now create different computation classes by mixing an arbitrary prompt style with the main computation partial class.

    class COMPUTE_A is
       include COMPUTE;
       include PROMPT_STYLE_A;
    end; -- class COMPUTE_A

    class COMPUTE_B is
       include COMPUTE;
       include PROMPT_STYLE_B;
    end; -- class COMPUTE_B

[back] [Abstract] [Copyright Notice] [Contents] [next]

Sather - A Language Manual