The Overloading Rule

Sather - A Language Tutorial
Prev | Chapter 5. Abstract Classes and Subtyping | Next

# 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 unnamedlink.

## 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

Prev | Home | Next
The `typecase` statement | Up | When Covariance Ails You