Sather - A Language Manual - Abstract Classes and Subtyping

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

# Sather - A Language Manual - Chapter 5
Abstract Classes and Subtyping

Abstract class definitions specify interfaces without implementations. Abstract class names must be entirely uppercase and must begin with a dollar sign `'$'` ; this makes it easy to distinguish abstract type specifications from other types, and may be thought of as a reminder that operations on objects of these types might be more expensive since they may involve dynamic dispatch. In order to motivate the notion of abstract classes, we will start by considering different implementations of a data structure.

## 5.1 Abstracting over Implementations

We will illustrate the need for abstraction by considering the implementation of a classic data structure, the stack. Objects are removed from a stack such that the last object to be inserted is the first to be removed (Last In First Out). For the sake of simplcity, we will define our stack to hold integers.

### 5.1.1 Implementing a Stack using an Array

The obvious implementation of a stack is using an array and a pointer to the top of the stack. When the stack outgrows the original array we allocate, we double the size of the array and copy the old elements over. This technique is known as amortized doubling and is an efficient way to allocate space for a datastructure whose size is not known when it is created.

    class ARR_STACK is
       private attr elems:ARRAY{INT};
       private attr index:INT; -- Points to the next location to insert

       create:SAME is
          res ::= new;
          res.elems := #ARRAY{INT}(5);
          res.index := 0;
          return res;
       end;

       push(e:INT) is
          if index > elems.size then
             new_elems:ARRAY{INT} := #ARRAY{INT}(index * 2);
             -- copy over the old elements
             loop
                new_elems.set!(elems.elt!);
             end;
             elems := new_elems;
          end;
          elems[index] := e;
          index := index + 1;
       end;

       pop:INT is
          index := index - 1;
          return elems[index];
       end;

       is_empty:INT is
          return index = 0;
       end;
    end;

It would be appropriate to also shrink the array when elements are popped from the stack, but we ignore this complexity for now.

### 5.1.2 A Stack Calculator

The stack class we defined can now be used in various applications. For instance, suppose we wish to create an calculator using the stack

    class RPN_CALCULATOR is
       private attr stack:ARR_STACK;

       create:SAME is
          res ::=new;
          res.stack := #ARR_STACK;
          return res;
       end;

       push(e:INT) is
          stack.push(e);
       end;

       add:INT is
          -- Add the two top two eleemnts
          if stack.is_empty then
             empty_err;
             return 0;
          end;
          arg1:INT := stack.pop;
          if stack.is_empty then
             empty_err;
             return 0;
          end;
          arg2:INT := stack.pop
          return arg1 + arg2;
       end;

       private empty_err is
          #ERR + "No operands available!"
       end;
    end;

This corresponds to a H-P style reverse polish notation calculator (RPN) where you first enter operands and then an operator.

### 5.1.3 A Linked List Representation of a Stack

An alternative implementation of a stack might make use of a chain of elements i.e. a linked list representation. Each link in the chain has a pointer to the next element

    class STACK_ELEM_HOLDER is
       readonly attr data:INT;
       attr next:INT_STACK_ELEM;

       create(data:INT):SAME is
          res ::= new;
          res.data := data;
          res.next := void;
          return res;
       end;
    end;

The whole stack is then constructed using a chain of element holders.

    class LINK_STACK is
       private attr head:STACK_ELEM_HOLDER;
       create:SAME is  res ::= new; return res; end;

       push(e:INT) is
          elem_holder ::= #STACK_ELEM_HOLDER(e);
          elem_holder.ext := head;
          head := elem_holder;
       end;

       pop:INT is
          res:INT := head.data;
          head := head.next;
       end;

       is_empty:BOOL is
          return void(head);
       end;
    end;

### 5.1.4 Switching Representations:Polymorphism

Each of these stack implementations has advantages and disadvantages (the trade-offs are not very significant in our example, but can be quite considerable in other cases). Either of these stacks could be used in our calculator. To use the linked list stack we would need to replace `ARR_STACK` by by `LINK_STACK`. wherever it is used.

It would be nice to be able to write code such that we could transparently replace one kind of stack by the other. If we are to do this, we would need to be able to refer to them indirectly, through some interface which hides which particular implementation we are using. Interfaces of this sort are described by abstract classes in Sather. An abstract class that describes the stack abstraction is

    abstract class $STACK is
       create:SAME;
       push(e:INT);
       pop:INT;
       is_empty:BOOL;
    end;

Note that the interface just specifies the operations on the stack, and says nothing about how they are implemented. We have to then specify how our two implementations conform to this abstraction. This is indicated in the definition of our implementations. More details on this will follow in the sections below.

    class ARR_STACK < $STACK is ... same definition as before ...

    class LINK_STACK < $STACK is ... same definition as before ...

The calculator class can then be written as follows

    class RPN_CALCULATOR is
       private attr stack:$STACK;
       create(s:$STACK):SAME is
          res ::= new;
          res.stack := s;
          return res;
       end;

       ... 'add' and 'push' behave the same
    end;

In this modified calculator, we provide a stack of our choice when creating the calculator. Any implementation that conforms to our stack abstraction my be used in place of the array based stackt

    s:LINK_STACK := #LINK_STACK;
    calc:RPN_CALCULATOR := #RPN_CAlCULATOR(s);
    calc.push(3);
    calc.push(5);
    #OUT + calc.add;          -- Prints out 8

## 5.2 Abstract Class Definitions

The body of an abstract class definition consists of a semicolon separated list of signatures. Each specifies the signature of a method without providing an implementation at that point. The argument names are required for documentation purposes only and are ignored.

    abstract class $SHIPPING_CRATE is
       destination:$LOCATION;
       weight:FLT;
    end; -- abstract class $SHIPPING_CRATE

Due to the rules of subtyping, which will be introduced lateron (See Type Conformance, section 5.5), there is one restriction on the signatures - `SAME` is permitted only for a return type or out arguments in an abstract class signature.

Abstract types can never be created! Unlike concrete classes, they merely specify an interface to an object, not an object itself. All you can do with an abstract type is to declare a variable to be of that type. Such a variable can point to any actual object which is a subtype of that abstract class. How we determine what objects such an abstract variable can point to is the subject of the next section.

Note that we can, of course, provide a create routine in the abstract class

    abstract class $SHIPPING_CRATE is
       create:SAME; ...

However, we can never call this creation routine on a void abstract class i.e. the following is prohibited

    crate: $SHIPPING_CRATE := #$SHIPPING_CRATE; -- ILLEGAL

In fact, all class calls (:: calls) are prohibited on abstract classes

    f:FLT := $SHIPPING_CRATE::weight;  -- ILLEGAL

Since abstract classes do not define objects, and do not contain shared attributes or constants, such calls on the class are not meaningful.

Example: An abstract employee

`$EMPLOYEE` illustrates an abstract type. `EMPLOYEE` and `MANAGER` are subtypes. Abstract type definitions specify interfaces without implementations.. Below, we will illustrate how the abstract type may be used.

    abstract class $EMPLOYEE is
       -- Definition of an abstract type.  Any concrete class that
       -- subtypes from this abstract class must provide these routines.
       name:STR;
       id:INT;
    end;

This abstract type definition merely states that any employee must have a name and an id.

More abstract class examples

Here's an example from the standard library. The abstract class `$STR` represents the set of types that have a way to construct a string suitable for output. All of the standard types such as `INT`, `FLT`, `BOOL` and `CPX` know how to do this, so they are subtypes of `$STR`. Attempting to subtype from `$STR` a concrete class that didn't provide a `str` method would cause an error at compile time.

    abstract class $STR is
       -- Ensures that subtypes have a 'str' routine
       str:STR;   -- Return a string form of the object
    end;

In this illegal abstract class, A and B do not conflict because their arguments are concrete and are not the same type. However, because the argument of C is abstract and unrelated it conflicts with both A and B. D does not conflict with A, B or C because it has a different number of parameters.

    abstract class $FOO is
       foo(arg:INT);      -- method A
       foo(arg:BOOL);     -- method B
       foo(arg:$FOO);     -- method C
       foo(a, b:INT);     -- method D
    end;

## 5.3 Subtyping

As promised, here is the other half of inheritance, subtyping. A subtyping clause (`'<'` followed by type specifiers) indicates that the abstract signatures of all types listed in the subtyping clause are included in the interface of the type being defined. In the example, the subtyping clause is

    abstract class $SHIPPING_CRATE < $CRATE is ...

The interface of an abstract type consists of any explicitly specified signatures along with those introduced by the subtyping clause.

Points to note about subtyping:

- Every type is automatically a subtype of `$OB`

- Only abstract types can be mentioned in the subtyping clause

- When a subtyping clause is used in a partial class, it enforces the basic subtyping rule using the stub routine.

- There must be no cycle of abstract types such that each appears in the subtype list of the next, ignoring the values of any type parameters but not their number.

- A subtyping clause may not refer to `SAME`.

- `SAME` is only permitted as a return type or on `out` arguments in abstract class signatures.

### 5.3.1 The Type Graph

We frequently refer to the Sather type graph, which is a graph whose nodes represent Sather types and whose edges represent subtyping relationships between sather types. Subtyping clauses introduce edges into the type graph. There is an edge in the type graph from each type in the subtyping clause to the type being defined. The type graph is acyclic, and may be viewed as a tree with cross edges (the root of the tree is `$OB`, which is an implicit supertype of all other types).

    abstract class $TRANSPORT is ...
    abstract class $FAST is ...
    abstract class $ROAD_TRANSPORT < $TRANSPORT is ...
    abstract class $AIR_TRANSPORT < $TRANSPORT, $FAST is ...
    class CAR < $ROAD_TRANSPORT is ...
    class DC10 < $AIR_TRANSPORT is ...

Since it is never possible to subtype from a concrete class (a reference, immutable or external class), these classes, CAR and DC10 form the leaf nodes of the type graph.

### 5.3.2 Dynamic Dispatch and Subtyping

Once we have introduced a typing relationship between a parent and a child class, we can use a variable of the type of the parent class to hold an object with the type of the child. Sather supports dynamic dispatch - when a function is called on a variable of an abstract type, it will be dispatched to the type of the object actually held by the variable. Thus, subtyping provides polymorphism.

An example: Generalizing Employees

To illustrate the use of dispatching, let us consider a system in which variables denote abstract employees which can be either `MANAGER` or `EMPLOYEE` objects. Recall the defintions of manager and employee

    class EMPLOYEE < $EMPLOYEE is ...
       -- Employee, as defined earlier

    class MANAGER < $EMPLOYEE is ...
       -- Manager as defined earlier

For the definition of these classes, see Code Inclusion and Partial Classes, chapter 4

The above defintions can then be used to write code that deals with any employee, regardless of whether it is a manager or not

    class TESTEMPLOYEE is
       main is
          employees:ARRAY{$EMPLOYEE} := #ARRAY{$EMPLOYEE}(3);
          -- employees is a 3 element array of employees
          i:INT := 0;
          wage:INT := 0;
          loop
             until!(i = employees.size);
             emp:$EMPLOYEE := employees[i];
             emp_wage:INT := emp.wage;
             -- emp.wage is a dispatched call on "'age'
             wage := wage + emp_wage;
          end;
          #OUT + wage + "\n";
       end;
    end;

The main program shows that we can create an array that holds either regular employees or managers. We can then perform any action on this array that is applicable to both types of employees. The `wage` routine is said to be `dispatched`. At compile time, we don't know which wage routine will be called. At run time, the actual class of the object held by the `emp` variable is determined and the `wage` routine in that class is called.

## 5.4 Supertyping

Unlike most other object oriented languages, Sather also allows the programmer to introduce types above an existing class. A supertyping clause (`'>'` followed by type specifiers) adds to the type graph an edge from the type being defined to each type in the supertyping clause. These type specifiers may not be type parameters (though they may include type parameters as components) or external types. There must be no cycle of abstract classes such that each class appears in the supertype list of the next, ignoring the values of any type parameters but not their number. A supertyping clause may not refer to `SAME`.

If both subtyping and supertyping clauses are present, then each type in the supertyping list must be a subtype of each type in the subtyping list using only edges introduced by subtyping clauses. This ensures that the subtype relationship can be tested by examining only definitions reachable from the two types in question, and that errors of supertyping are localized.You define supertypes of already existing types. The supertype can only contain routines that are found in the subtype i.e. it cannot extend the interface of the subtype.

    abstract class $IS_EMPTY > $LIST, $SET is
       is_empty:BOOL;
    end;

### 5.4.1 Using supertyping

The main use of supertyping arises in defining appropriate type bounds for parametrized classes, and will be discussed in the next chapter (see Supertyping and Type Bounds, subsection 6.3.2).

## 5.5 Type Conformance

In order for a child class to legally subtype from a parent abstract class, we have to determine whether the signatures in the child class are consistent with the signatures in the parent class. The consistency check must ensure that in any code, if the parent class is replaced by the child class, the code would continue to work. This guarantee of substuitability which is guaranteed to be safe at compile time is at the heart of the Sather guarantee of type-safety.

### 5.5.1 Contravariant conformance

The type-safe rule for determining whether a signature in a child class is consistent with the definition of the signature in the parent class is referred to as the conformance rule[12]. The rule is quite simple, but counter-intuitive at first. Assume the simple abstract classes which we will use for argument types

    abstract class $UPPER is ...
    abstract class $MIDDLE < $UPPER is...
    abstract class $LOWER < $MMIDDLE is ...

If we now have an abstract class with a signature

    abstract class $SUPER is
       foo(a1:$MIDDLE, out a2:$MIDDLE, inout a3:$MIDDLE):$MIDDLE;
    end;

What are the arguments types of `foo` in a subytpe of `$SUPER`? The rule says that in the subtype definition of foo

- Normal arguments (with the mode in) must have the same type or a supertypes

- `out` arguments and return values must have the same type or a subtype

- `inout` arguments must have the same type

Thus, a valid subtype of `$SUPER` is

    abstract class $SUPER is
       foo(a1:$MIDDLE, out a2:$MIDDLE, inout a3:$MIDDLE):$MIDDLE;
    end;

We will explain this rule and its ramifications using an extended example.

Suppose we start with herbivores and carnivores, each of which are capable of eating

    abstract class $HERBIVORE is
       eat(food:$PLANT);
    ...

    abstract class $CARNIVORE is
       eat(food:$MEAT);
    ...

    abstract class $FOOD is ...
    abstract class $PLANT < $FOOD is...
    abstract class $MEAT < $FOOD is...

What does not work

It would appear that both herbivores and carnivores could be subtypes of omnivores.

    abstract class $OMNIVORE is eat(food:$FOOD);
    abstract class $CARNIVORE < $OMNIVORE is ...
    abstract class $HERBIVORE < $OMNIVORE is ...

However, subtyping conformance will not permit this! The argument to eat in `$HERBIVORE` is `$PLANT` which is not the same as or a supertype of `$FOOD`, the argument to eat in `$OMNIVORE`.

To illustrate this, consider a variable of type `$OMNIVORE,` which holds a herbivore.

    cow:$HERBIVORE := #COW; -- assigned to a COW object
    animal:$OMNIVORE := cow;
    meat:$MEAT;
    animal.eat(meat);

This last call would try to feed the animal meat, which is quite legal according to the signature of `$OMNIVORE::eat($FOOD)`, since `$MEAT` is a subtype of `$FOOD`. However, the animal happens to be a cow, which is a herbivore and cannot eat meat.

What does work

When contravariance does not permit a subtyping relationship this is usually an indication of an exceptional case or an error in our conceptual understanding. In this case, we note that omnivores are creatures that can eat anything. But a herbivore really is not an omnivore, since it cannot eat anything. More importantly, a herbivore could not be substuted for an omnivore. It is, however, true that an omnivore can act as both a carnivore and a herbivore.

    abstract class $CARNIVORE is
       eat(food:$MEAT);
    ...

    abstract class $HERBIVORE is
       eat(food:$PLANT);
    ...

    abstract class $OMNIVORE < $HERBIVORE, $CARNIVORE is
       eat(food:$FOOD);
    ...

The argument of eat in the omnivore is `$FOOD`, which is a supertype of `$MEAT`, the argument of eat in `$CARNIVORE`. `$FOOD` is also a supertype of `$PLANT` which is the argument of eat in `$HERBIVORE`.

### 5.5.2 Subtyping = substitutability

A key distinction is that between is-a and as-a relationships. When a class, say `$OMNIVORE` subtypes from another class such as `$CARNIVORE`, it means that an omnivore can be used in any code which deals with carnivores i.e. an omnivore can substitute for a carnivore. In order for this to work properly, the child class omnivore must be able to behave as-a carnivore. In many cases, an is-a relationship does not satisfy the constraints required by the as-a relationship. The contravariant conformance rule captures the necessary as-a relationship between a subtype and a supertype.

## 5.6 The `typecase` statement

It is sometimes necessary to bypass the abstraction and make use of information about the actual type of the object to perform a particular action. Given a variable of an abstract type, we might like to make use of the actual type of the object it refers to, in order to determine whether it either has a particular implementation or supports other abstractions.

The `typecase` statement provides us with the ability to make use of the actual type of an object held by a variable of an abstract type.

    a:$OB := 5;
    ... some other code...
    res:STR;

    typecase a
    when INT then                -- 'a' is of type INT in this branch
       #OUT + "Integer result: " + a;
    when FLT then                -- 'a' is of type FLT in this branch
       #OUT + "Real result: " + a;
    when $STR then               -- 'a' is $STR and supports '.str'
       #OUT + "Other printable result: " + a.str;
    else
       #OUT + "Non printable result";
    end;

The typecase must act on a local variable or an argument of a method.On execution, each successive type specifier is tested for being a supertype of the type of the object held by the variable. The statement list following the first matching type specifier is executed and control passes to the statement following the `typecase`.

Points to note

- It is not legal to assign to the `typecase` variable within the statement lists.

- If the object's type is not a subtype of any of the type specifiers and an `else` clause is present, then the statement list following it is executed.

- It is a fatal error for no branch to match in the absence of an `else` clause.

- If the value of the variable is `void` when the `typecase` is executed, then its type is taken to be the declared type of the variable. In the above example, the declared type of `a` is `$OB` , which does not match any of the branches, so the `else` clause is taken

- The variable of the typecase must be a local variable or a method argument.

- If the `typecase` appears in an iterator, then the mode of the argument must be `once`; otherwise, the type of object that such an argument holds could change.

- The typecase does not search for the branch with the tightest match - it goes down the first branch that matches.

- After a branch has been selected, the typecase tries to cast the variable to as narrow a type as possible - if the declared type of the variable is actually stronger than (a subtype of) the chosen branch, then the variable will keep the stronger, declared type. For instance

      a:$SET{INT};
      typecase a
      when INT then ... -- a will never get here, INT is not < $SET{INT}
      when $OB then ...
         -- a has the type of $SET{INT} which is stronger than $OB

Typecase Example

For instance, suppose we want to know the total number of subordinates in an array of general employees.

    peter ::= #EMPLOYEE("Peter",1);      -- Name = "Peter", id = 1
    paul  ::= #MANAGER("Paul",12,10);    -- id = 12,10 subordinates
    mary  ::= #MANAGER("Mary",15,11);    -- id = 15,11 subordinates
    employees: ARRAY{$EMPLOYEE} := |peter,paul,mary|;
    totalsubs: INT := 0;
    loop employee:$EMPLOYEE := employees.elt!; -- yields array elements
       typecase employee
       when MANAGER then
          totalsubs := totalsubs + employee.numsubordinates;
       else
       end;
    end;
    #OUT + "Number of subordinates: " + totalsubs + "\n";

Within each branch of the typecase, the variable has the type of that branch (or a more restrictive type, if the declared type of the variable is a subtype of the type of that branch).

## 5.7 The Overloading Rule

We mentioned an abridged form of the overloading rule in the chapter on Classes and Objects. That simple overloading rule was very limited - it only permitted overloading based on the number of arguments and the presence or absence of a return value. Here, it is generalized.

As a preliminary warning:the overloading are flexible, but are intended to support the coexistance of multiple functions that have the same meaning, but differ in some implementation detail.Calling functions that do different things by the same name is wrong, unwholesome and severely frowned upon! Hence, using the function name times with different number of arguments to mean

### 5.7.1 Extending Overloading

Overloading based on Concrete Argument Types

However, we often want to overload a function based on the actual type of the arguments. For instance, it is common to want to define addition routines (`plus`) that work for different types of values. In the `INT` class, we could define

    plus(a:INT):INT is ...
    plus(a:FLT):INT is ...

We can clearly overload based on a the type of the argument if it is a non-abstract class - at the point of the call, the argument can match only one of the overloaded signatures.

Overloading based on Abstract Argument Types

Extending the rule to handle abstract types is not quite as simple. To illustrate the problem, let us first introduce the `$STR` abstract class

    abstract class $STR is
       str:STR;
    end;

The `$STR` absraction indicates that subtypes provide a routine that renders a string version of themselves. Thus, all the common basic types such as `INT`, `BOOL` etc. are subtypes of `$STR` and provide a `str: STR` routine that returns a string representation of themselves.

Now consider the interface to the `FILE` class. In the file class we would like to have a general purpose routine that appends any old `$STR` object, by calling the str routine on it and then appending the resulting string. This allows us to append any subtype of `$STR` to a file at the cost of a run-time dispatch. We also want to define more efficient, special case routines (that avoid the dispatched call to the str routine) for common classes, such as integers

    class FILE is
       -- Standard output class
       plus(s:$STR) is ...   -- (1)
       plus(s:INT) is ...    -- (2)
    end;

The problem arises at the point of call

    f:FILE := FILE::open_for_read("myfile");
    a:INT := 3;
    f+a;

Now which plus routine should we invoke? Clearly, both routines are valid, since `INT` is a subtype of `$STR`. We want the strongest or most specific among the matching methods, (2) in the example above. Though the notion of the most specific routine may be clear in this case, it can easily get murky when there are more arguments and the type graph is more complex.

The Demon of Ambiguity

It is not difficult to construct cases where there is no single most specific routine. The following example is hypotheical and not from the current Sather library, but illustrates the point. Suppose we had an abstraction for classes that can render a binary versions of themselves. This might be useful, for instance, for the floating point classes, where a binary representation may be more compact and reliable than a decimal string version

    abstract class $BINARY_PRINTABLE is
       -- Subtypes can provide a binary version of themselves
       binary_str:STR;
    end;

Now suppose we have the following interface to the `FILE` class

    class FILE is
       plus(s:$STR) is ...        -- (1)
       plus(s:$BINARY_STR) is ... -- (2)
       plus(s:INT) is ...         -- (3)
    end;

Now certain classes, such as `FLT` could subtype from `$BINARY_STR` instead of from `$STR`. Thus, in the following example, second plus routine would be seletected

    f:FILE;
    f + 3.0;

Everything is still fine, but suppose we now consider

    class FLTD < $BINARY_STR, $STR is
       binary_str:STR is ... -- binary version
       str:STR is ...        -- decimal version

The plus routine in `FILE` cannot be unambiguously called with an argument of type `FLTD` i.e. a call like '`f+3.0d'` is ambiguous. None of the 'plus' routines match exactly; (1) and (2) both match equally well.

The above problem arises because neither (1) nor (2) is more specific than the other - the problem could be solved if we could always impose some ordering on the overloaded methods, such that there is a most specific method for any call.

We could resolve the above problem by ruling the `FILE` class to be illegal, since there is a common subtype to both `$STR` and `$BINARY_STR`, namely `FLTD`. Thus, a possible rule would be that overloading based on abstract arguments is permitted, provided that the abstract types involved have no subtypes in common.

However, the problem is somewhat worse than this in Sather, since both subtyping and supertyping edges can be introduced after the fact. Thus, if we have the following definition of `FLTD`

    class FLTD < $BINARY_STR is
       binary_str:STR is ...
       str:STR is ...

the file class will work. However, at a later point, a user can introduce new edges that cause the same ambiguity described above to reappear!

    abstract class $BRIDGE_FLTD < $STR > FLTD is
    end;

Adding this new class introduces an additional edge into the type graph and breaks existing code.

The essense of the full-fledged overloading rule avoids this problem by requiring that the type of the argument in one of the routines must be known to be more specific than the type of the argument in the corresponding position in the other routine. Insisting that a subtyping relationship between corresponding arguments must exist, effectively ensures that one of the methods will be more specific in any given context. Most importantly, this specificity cannot be affected by the addition of new edges to the type graph. Thus, the following definition of `$BINARY_STR` would permit the overloading in the `FILE` class to work properly

    abstract class $BINARY_STR < $STR is
       binary_str:STR;
    end;

When the `'plus'` routine is called with a `FLTD`, the routine `'plus($BINARY_STR)'` is unambiguously more specific than `'plus($STR)'`.

### 5.7.2 Permissible overloading

Two signatures (of routines or iterators) can overload, if they can be distinguised in some manner- thus, they must differ in one of the following ways

Overload 1.: The presence/absence of a return value

Overload 2.: The number of arguments

Overload 3.: In at least one case corresponding arguments must have different marked modes (in and once modes are not marked at the point of call and are treated as being the same from the point of view of overloading).

Overload 4.: In at least one of the `in, once` or `inout` argument positions: (a) both types are concrete and different or (b) there is a subtyping relationship between the corresponding arguments i.e. one must be more specific than the other. Note that this subtyping ordering between the two arguments cannot be changed by other additions to the type graph, so that working libraries cannot be broken by adding new code.

Note that this definition of permissible permissible coexistance is the converse of the definition of conflict in the specification. That is, if two signatures cannot coexist, they conflict and vice-versa.

    abstract class $VEC is ...
    abstract class $SPARSE_VEC < $VEC is ...
    abstract class $DENSE_VEC < $VEC is...

    class DENSE_VEC < $DENSE_VEC is ...
    class SPARSE_VEC < $SPARSE_VEC is ....

Given the above definitions of vectors, we can define a multiply and add routine in the matrix class

    abstract class $MATRIX is
    -- (1)
       mul_add(by1:$VEC, add1:$SPARSE_VEC);

    -- (2)
       mul_add(by2:$DENSE_VEC,  add2:$VEC);
       -- (1) and (2) can overload, since the arg types can be ordered
       -- by2:$DENSE_VEC < by1:$VEC,
       -- add2:$VEC       > add1:$SPARSE_VEC

    -- (3)
       mul_add(by3:DENSE_VEC, add3:SPARSE_VEC);
       -- (3) does not conflict with the (1) and (2) because there
       --     is a subtyping relation between corresponding arguments.
       -- (vs 1) by3:DENSE_VEC    < by1:$VEC ,
       --        add3:SPARSE_VEC < add1:$SPARSE_VEC
       -- (vs 2) by3:DENSE_VEC    < by2:$DENSE_VEC ,
       --        add3:SPARSE_VEC < add2:$VEC
    end;

While any of the above conditions ensures that a pair of routines can co-exist in an interface, it still does not describe which one will be chosen during a call.

Finding matching signatures

When the time comes to make a call, some of the coexisting routines will match - these are the routines whose arguments are supertypes of the argument types in the call. Among these matching signatures, there must be a single most specific signature. In the example below, we will abuse sather notation slightly to demonstrate the types directly, rather than using variables of those types in the arguments

    f:$MATRIX;
    f.mul_add(DENSE_VEC, SPARSE_VEC);     -- Matches (1), (2) and (3)
    f.mul_add($DENSE_VEC, $SPARSE_VEC);   -- Matches (1) and (2)
    f.mul_add($DENSE_VEC, $DENSE_VEC);    -- Matches (2)
    f.mul_add($SPARSE_VEC, SPARSE_VEC);   -- Matches (1)

Finding a most specific matching signature

For the method call to work, the call must now find an unique signature which is most specific in each argument position

    f:$MATRIX;
    f.mul_add(DENSE_VEC, SPARSE_VEC)      -- (3) is most specific
    f.mul_add($DENSE_VEC, $DENSE_VEC);    -- Only one match
    f.mul_add($SPARSE_VEC, $SPARSE_VEC);  -- Only one match

The method call `'f.mul_add($DENSE_VEC, $SPARSE_VEC)'` is illegal, since both (1) and (2) match, but neither is more specific.

More examples

Let us illustrate overloading with some more examples. Consider `'foo(a:A, out b:B);'`

All the following can co-exist with the above signature

    foo(a:A, out b:B):INT    -- Presence return value (Overload 1)
    foo(a:A)                 -- Number of arguments (Overload 2)
    foo(a:A, b:B)            -- Mode of second argument (Overload 3)
    foo(a:B, out b:B)        -- Different concrete types in
             -- the first argument (Overload 4a)

The following cannot be overloaded with `foo(a:A,out b:B):INT;`

    foo(a:A,b:B):BOOL;  -- Same number, types of arguments,
                        -- both have a return type.
    -- Difference in actual return type cannot be used to overload

For another example, this time using abstract classes, consider the mathematical abstraction of a ring over numbers and integers. The following can be overloaded with the `'plus'` function in a class which describes the mathematical notion of rings

    abstract class $RING is
       plus(arg:$RING):$RING;
    ...

    abstract class $INT  < $RING is
       plus(arg:$INT):$RING;
       --  By Overload 4 since he type of  arg:$INT  < arg:$RING
    ...

    abstract class $CPX < $RING is
       plus(arg:$CPX):$RING;
       -- By Overload 4b, since the type of arg:$CPX < arg:$RING
    ...

The overloading works because there is a subtyping relationship between the arguments 'arg' to 'plus' The following overloading also works

    abstract class $RING is
       mul_add(ring_arg1:$RING, ring_arg2:$RING);
    ...

    abstract class $INT < $RING is
       mul_add(int_arg1:$INT, int_arg2:$INT);
       -- int_arg1:$INT  < ring_arg:$INT and
       -- int_arg2:$INT < ring_arg2:$INT
    ...

Now there is a subtyping relationship between `$INT::mul_add` and `$RING::mul_add` for both `'arg1'` and `'arg2'`, but there is no subtyping

This somewhat complex rule permits interesting kinds of overloading that are needed to implement a kind of statically resolved, type-safe co-variance which is useful in the libraries, while not sacrificing compositionality. Externally introducing subtyping or supertyping edges into the typegraph cannot suddenly break overloading in a library.

### 5.7.3 Overloading as Statically resolved Multi-Methods

For the curious reader, we would like to point out a connection to the issue of co and contra-variance. It was this connection that actually motivated our overloading rules. The first point to note is that overloading is essentially like statically resolved multi-methods i.e. methods that can dispatch on more than one argument. Overloaded methods are far more restricted than multi-methods since the declared type must be used to perform the resolution. The second point to note is that multi-methods can permit safe 'covariance' of argument types. For instance, consider the following abstractions

    abstract class $FIELD_ELEMENT is
       add(f:$FIELD_ELEMENT):$FIELD_ELEMENT;
    ...

    abstract class $NUMBER < $FIELD_ELEMENT is
       add(f:$NUMBER):$NUMBER
    ...

    abstract class $INTEGER < $NUMBER is
       add(f:$INTEGER):$INGEGER
    ...

Note that all the above definitions of the `'plus'` routines safely overload each other. As a consequence, it is possible to provide more specific versions of functions in sub-types.

### 5.7.4 Conflicts when subtyping

When we described subtyping earlier, we said that the interface of the abstract class being defined is augmented by all the signatures of the types in the subtyping clause. But what if some of these supertypes contain conflicting signatures?

It is important to note that a conflict occurs when two signatures are so similar that they cannot co-exist by the over-loading rules. This happens when there is not even one argument where there is a sub- or supertyping relationship or where both arguments are concrete. As a consequence, you can always construct a signature that is more general than the conflicting signatures

    abstract class $ANIMAL is ...
    abstract class $PIG < $ANIMAL is ...
    abstract class $COW < $ANIMAL is ...
    abstract class $COW_FARM is
       has(a:$COW);
    ...

    abstract class $PIG_FARM is
       has(a:$PIG);
    ...

    abstract class $ANIMAL_FARM < $COW_FARM, $PIG_FARM is
       -- The signatures for has(a:$COW) and has(a:$PIG) must
       -- be generalized
       has(a:$ANIMAL);
       -- $ANIMAL is a supertype of $COW and $PIG, so this 'has'
       -- conforms to both the supertype 'has' signatures
    ...

In the above example, when we create a more general farm, we must provide a signature that conforms to all the conflicting signatures by generalizing the in arguments. If the arguments in the parent used the `out` mode, we would have to use a subtype in the child. A problem is exposed if the mode of the arguments in the parents is `inout`

    abstract class $COW_FARM is
       processes(inout a:$COW);
    end;

    abstract class $PIG_FARM is
       processes(inout a:$PIG);
    end;

    -- ILLEGAL! abstract class $ANIMAL_FARM < $COW_FARM, $PIG_FARM is
    -- No signature can conform to both the 'processes' signatures
    -- in the $COW_FARM and $PIG_FARM

### 5.7.5 Conflicts during code inclusion

Since Sather permits inclusion from mulitple classes, conflicts can easily arise between methods from different classes. The resolution of inclusion conflicts is slightly different for attributes than it is for methods, so let us consider them separately.

Conflicting Methods

  1. First, let us consider the resolution method for routines. Conflicts can occur between methods in different classes that have been included and must be resolved by renaming the offending feature in all but one of the included classes.

      class PARENT1 is
         foo(INT):INT;
      ...

      class PARENT2 is
         foo(INT):BOOL; -- conflicts with PARENT1::foo
      ...

      class PARENT3 is
         foo(INT):FLT;  -- would similarly conflict
      ...

      class CHILD is
         include PARENT1 foo -> parent1_foo;
         -- Include and rename away the routine 'foo'
         include PARENT2 foo -> parent2_foo;
         -- Include and rename away the routine 'foo'
         include PARENT3;
         -- Use the routine from this class
      ...

  2. The other way to resolve method conflicts is to explicitly define a method in the child class that will then over-ride all the parent methods.

      class CHILD is
         include PARENT1;
         include PARENT2;
         include PARENT3;

         foo(INT):BOOL is
         -- over-rides all the included, conflicting routines.
      end;

Conflicting Attributes

With conflicting attributes (including shareds and consts), the offending attributes must be renamed away, even if they are going to be replaced by other attributes i.e. Method 2 described above is not allowed for attributes:

    class PARENT is
       attr foo:INT;
    ...

    class CHILD is
       foo:BOOL;   -- ILLEGAL!
     -- Conflicts with the included reader for 'foo' i.e. foo:INT
    ...

Also the implicit reader and writer routines of attributes defined in the child must not conflict with routines in a parent

    class PARENT is
       foo(arg:INT);
    ...

    class CHILD is
       include PARENT;
       -- ILLEGAL! attr foo:INT;
       -- the writer routine foo(INT) conflicts
       -- with the writer for the include attribute foo(INT)
    ...

In other words, as far as attributes are concerned, they must always be explicitly renamed away - they are never silently over-ridden.

### 5.7.6 Points to note

- It is not possible to overload based based solely on `out` or `inout` arguments (by the pre-condition for applying the overload rule 4a and 4b)

- When a class explicitly defines a signature and includes a conflicting signature from another class, the included signature is over-ridden. This might lead to included signatures unexpectedly disappearing, instead of overloading.

- In certain special cases, subtyping from two classes with conflicting signatures that use `out` or `inout` arguments might not be possible, since the conflict cannot be resolved.

### 5.7.7 Overloading in Parametrized Classes

For details on the overloading rule for parametrized classes, see Overloading, section 6.5.

### 5.7.8 Why not use the return type to resolve conflicts?

According to the current overloading rules, the type of the return value and `out` arguments cannot be used to differentiate between methods in the interface. There is no theoretical reason to disallow this possibility. However permitting overloading based on such return values involves significant implementation work and was not needed for the usages we envisaged. Thus, overloading is not permitted based on differences in the return type (or `out` arguments, which are equivalent to return types) of a method

## 5.8 When Covariance Ails You

In some cases, however, one type can substitute for the other type but with a few exceptions. There are several ways to deal with this problem when it occurs.

[This section attempts to provide some insight into dealing with covariance. It is not essential to understanding the language, but might help in the design of your type hierarchy.]

### 5.8.1 But don't animals eat food?

We will consider the definition of an animal class, where both herbivores and carnivores are animals.

    abstract class $ANIMAL is
       eat(food:$FOOD);
    ...

    abstract class $HERBIVORE < $ANIMAL is ...
    abstract class $CARNIVORE < $ANIMAL is ...

The problem is similar to that in the previous section, but is different in certain ways that lead to the need for different solutions

### 5.8.2 Solution 1: Refactor the type hierarchy

The ideal solution would be to do what we did in the previous section - realize the conceptual problem and rearrange the type hierarchy to be more accurate. There is a difference in this case, though. When considering omnivores, the 'eat' operation was central to the definition of the subtyping relationship. In the case of animals, the eat operation is not nearly as central - the subtyping relationship is determined by many other features, completely unrelated to eating. It would be unreasonable to force animals to be subtypes of carnivores or herbivores.

### 5.8.3 Solution 2: Eliminate the offending method

A simple solution would be to determine whether we really need the 'eat' routine in the animal class. In human categories, it appears that higher level categories often contain features that are present, but vary greatly in the sub-categories. The feature in the higher level category is not "operational" in the sense that it is never used directly with the higher level category. It merely denotes the presence of the feature in all sub-categories.

Since we do not know the kind of food a general animal can eat, it may be reasonable to just omit the 'eat' signature from the definition of $ANIMAL. We would thus have

### 5.8.4 Solution 3: Dynamically Determine the Type

Another solution, that should be adopted with care, is to permit the 'eat($FOOD)' routine in the animal class, and define the subclasses to also eat any food. However, each subclass dynamically determines whether it wants to eat a particular kind of food.

    abstract class $ANIMAL is
       eat(arg:$FOOD);
    ...

    abstract class $HERBIVORE < $ANIMAL is ... -- supports eat(f:$FOOD);

    class COW < $HERBIVORE is
       eat(arg:$FOOD) is
          typecase arg
          when $PLANT then ... -- eat it!
          else raise "Cows only eat plants!"; end;
       end;
    end;

The 'eat' routine in the COW class accepts all food, but then dynamically determines whether the food is appropriate i.e. whether it is a plant.

This approach carries the danger that if a cow is fed some non-plant food, the error may only be discovered at run-time, when the routine is actually called. Furthermore, such errors may be discovered after an arbitrarily long time, when the incorrect call to the 'eat' routine actually occurs during execution.

This loss of static type-safety is inherent in languages that support co-variance, such as Eiffel. The problem can be somewhat ameliorated though the use of type-inference, but there will always be cases where type-inference cannot prove that a certain call is type-safe.

Sather permits the user to break type-safety, but only through the use of a typecase on the arguments. Such case of type un-safety uses are clearly visible in the code and are far from the default in user code.

### 5.8.5 Solution 4: Parametrize by the Argument Type

Another typesafe solution is to parametrize the animal abstraction by the kind of food the animal eats.

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

Sather - A Language Manual