Contents

Inheritance

Classes can inherit from multiple parent classes. Fields of the parent classes are "merged" into the resulting class using a breadth first search. For example, in the following situation

class X(A,B,C)
        ...
end

The fields of A, B and C are taken first, then the parents of A, B and C and then the parents of the parents, and so on. If a class is encountered more than once during this process, it is simply ignored on the second and subsequent occasions.

Restrictions

There are some restrictions on what can be inherited, as follows.

final class Parent()
end

class Child(Parent)
end
class Parent()
   public final f()
   end
end

class Child(Parent)
   public f()
   end
end

Field name clashes

It may be that a field name is encountered more than once whilst traversing the parent classes. If this happens, then the first occurence is always the one that is inherited into the resulting class. However, a compilation error may also be signalled. This will happen UNLESS

Looked at from another viewpoint, this table shows the permissible combinations, with an 'X' meaning disallowed and a '+' meaning allowed.

First def'n Instance var Static var Instance method Static method
Subsequent def'n
Instance var X X X X
Static var + + + +
Instance method X X + X
Static method + + + +

For an example of a permissible clash, consider the following:-

import io

class Parent()
   public static x

   private static init()
      x := 100    # Meaning Parent.x := 100
   end
end

class Child(Parent)
   public x

   public new()
      x := 99     # Meaning self.x := 99
      return
   end
end

procedure main()
   local z
   z := Child()
   write(z.x) # -> 99
   write(Parent.x) # -> 100
end

Here, Child inherits the instance field x from its own definition. The second static x still resides within the space reserved for it in Parent, so there is no problem. However, if we swap the declarations around, then a problem arises :-

class Parent()
   public x

   public new()
      x := 99   # Meaning self.x  ... not okay, Child's x is static
      return
   end
end

class Child(Parent)
   static public x

   private static init()
      x := 100  # Meaning Child.x ... okay
   end
end

procedure main()
   local z
   z := Child()
end

If this program were allowed to compile, it wouldn't work because Child would have a static field x, but the reference to x in x := 99 would attempt to assign to self.x, ie a non-existent instance field. This would cause a runtime error.

Implemented classes

For any particular class, this term is used to refer to the following group of classes :-

So if we have the following class hierarchy :-

class Animal()
class Pet()
class Mammal(Animal)
class Dog(Mammal,Pet)

Then the implemented classes of Dog are Dog, Mammal, Animal and Pet.

If we have an instance of Dog, then the builtin function is() will succeed for just the implemented classes. Thus :-

d := Dog()
is(d, Pet)  # Yes
is(d, Cat)  # No
is(d, Mammal) # Yes
... etc

The implemented classes of a particular class can be easily enumerated using the library function lang.Class.get_implemented_classes().

Accessing overridden methods

It is often the case that a class overrides an instance method, but still wants to access the overridden method in the parent class. This can be done using the following syntax :-

import io

class Parent()
   public func()
      write("Parent.func")
   end
end

class Child(Parent)
   public func()
      write("Child.func")
      # Call the overridden method
      Parent.func()
   end
end

procedure main()
   local z
   z := Child()
   z.func()
end

The expression Parent.func() is used to invoke the overridden method. Such an expression can only be used from within an instance method of one of Parent's implemented classes.

Specifying inheritance order

Sometimes it is desirable to explicitly specify the order of inheritance, especially when a class can be inherited in more than one way in an inheritance hierarchy. For example, consider the following classes :-

Download mixin.icn

import io

class Parent()
   public f()
      write("\tParent.f()")
   end
end

class Child1(Parent)
   public f()
      write("\tChild1.f()")
   end
end

class Child2(Child1)
end

class Child3(Child2)
end

class Mixin(Parent)
   public g()
      write("Mixin.g()")
      f()
   end
end

class Child4a(Child3,Mixin)
end

class Child4b(Child3,Child2,Mixin)
end

procedure main()
   Child4a().g()
   Child4b().g()
end

Here there is a simple inheritance hierarchy several levels deep, with Parent at the top and Child4a and Child4b at the bottom. There is also a "mixin" class, which both Child4a and Child4b use. Running the program reveals a problem with Child4a, as follows :-

Mixin.g()
        Parent.f()
Mixin.g()
        Child1.f()

The problem is that the method f() is no longer being overridden as expected. What is happening can be seen by working out the order in which classes are processed in the inheritance hierarchy. As each class is encountered, its direct superclasses are added to the end of the list of classes to process. The list starts with the class itself. As a class comes to the front of the list and is removed, its fields are added to the resulting class, with those encountered first having priority. Classes already seen are simply ignored.

So for Child4a the list is as follows at each step :-

Child4a                  (Start with the class itself)
Child3,Mixin             (Child4a removed, its parent classes are added)
Mixin,Child2             (Child3 removed, its parent (Child2) added)
Child2,Parent            (Mixin removed, its parent (Parent) added)
Parent,Child1            (Child2 removed, its parent (Child1) added)
Child1                   (Parent removed, it has no parent class)
Parent                   (Child1 removed, and its parent (Parent) added)
<empty>                  (Parent removed, and ignored as already seen)

The important point to note here is that Parent is encountered at the front of the list before Child1. Therefore Parent's f() takes priority over Child1's.

Now consider the sequence for Child4b :-

Child4b                  (Start with the class itself)
Child3,Child2,Mixin      (Child4b removed, its parent classes are added)
Child2,Mixin,Child2      (Child3 removed, its parent (Child2) added)
Mixin,Child2,Child1      (Child2 removed, its parent (Child1) added)
Child2,Child1,Parent     (Mixin removed, its parent (Parent) added)
Child1,Parent            (Child2 removed, and ignored as already seen)
Parent,Parent            (Child1 removed, and its parent (Parent) added)
Parent                   (Parent removed, it has no parent class)
<empty>                  (Parent removed, and ignored as already seen)

In this case we see that we encounter Child1 before Parent, so we get the desired method from Child1.

ieval can be used to check where fields have been inherited from, as shown below :-

$ ieval -l mixin.icn
> dir(Child4a)
class Child4a(Child3, Mixin)
   at line 28 in /home/rparlett/mixin.icn
   implements Parent, Child1, Child2, Child3, Mixin, Child4a
f    Parent    2    Method Public
g    Mixin     1    Method Public
> dir(Child4b)
class Child4b(Child3, Child2, Mixin)
   at line 31 in /home/rparlett/mixin.icn
   implements Parent, Child1, Child2, Child3, Mixin, Child4b
f    Child1    2    Method Public
g    Mixin     1    Method Public
>

Note that it is necessary to rename or delete the main procedure in mixin.icn, since ieval expects a library file (with no main).

The second column in the field table gives the source class for each field.

Contents