Classes can inherit from multiple parent classes. Fields of the parent classes are “merged” into the resulting class by a process which, firstly, involves linearizing the graph of superclasses (ie: flattening it into a simple list). After this is done, the resulting list of classes is processed in order, adding fields as they occur. Fields which come first take priority; they override fields which come later in the list.
The algorithm used to perform the flattening is described below.
There are some restrictions on what can be inherited, as follows.
A class cannot inherit from a superclass declared as final
. For example, this is not allowed :-
final class Parent()
end
class Child(Parent)
end
A class cannot inherit (directly or indirectly) from a superclass declared as protected
, unless it is in the same package. For example, this is not allowed :-
package first
protected class C()
...
end
...
package second
import first(C)
class Y(C) # Error (outside of package)
end
A method which was declared final
or private
cannot be overridden. For example, this is not allowed :-
class Parent()
public final f()
end
private g()
end
end
class Child(Parent)
public override f() # Error
end
public override g() # Error
end
end
If (and only if) a method overrides another, then it must be declared with the override
modifier :-
class Parent()
public f()
end
public g()
end
end
class Child(Parent)
public override f() # OK
end
public g() # Error (needs override)
end
public override h() # Error (doesn't override)
end
end
A method which was declared package
cannot be overridden (directly or indirectly) except by a method in a subclass within the same package. For example :-
package first
class C()
package f()
end
end
class X(C)
package override f() # OK
end
end
...
package second
import first(C)
class Y(C)
public override f() # Error (outside of package)
end
end
It may also happen that there is a disallowed name clash - this is described in the next section.
It may be that a field name is encountered more than once whilst traversing the flattened list of 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 | X | + | X | + |
Instance method | X | X | + | X |
Static method | X | + | X | + |
This term is used to describe the set of classes in the list created by the linearization algorithm. For any class, it will comprise :-
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 Class.get_implemented_classes
. Note that this method doesn’t produce the classes in any specific order.
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 override 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, and the target method must be in one of the instance’s implemented classes.
Object Icon uses the C3 superclass linearization algorithm to calculate the implemented classes of a class. The algorithm turns the inheritance graph of the class into a simple list of classes, which can then be used to calculate the fields of the class.
This algorithm is also used by several other languages which support multiple inheritance.
ieval
can be used to see how this algorithm is applied to a particular class. For example :-
$ ieval -i io
> c3(PipeStream)
C3: io.PipeStream(io.FileStream)
C3: io.FileStream(io.DescStream)
C3: io.DescStream(io.Stream, lang.NoCopy)
C3: io.Stream(util.HasMode)
C3: util.HasMode() = util.HasMode
Added: io.Stream
Added: util.HasMode
Result: 2 classes
C3: lang.NoCopy(lang.SelfClone, lang.Unencodable)
C3: lang.SelfClone(lang.ObjectClone)
C3: lang.ObjectClone() = lang.ObjectClone
Added: lang.SelfClone
Added: lang.ObjectClone
Result: 2 classes
C3: lang.Unencodable(lang.ObjectCodec)
C3: lang.ObjectCodec() = lang.ObjectCodec
Added: lang.Unencodable
Added: lang.ObjectCodec
Result: 2 classes
Added: lang.NoCopy
Added: lang.SelfClone
Added: lang.ObjectClone
Added: lang.Unencodable
Added: lang.ObjectCodec
Result: 5 classes
Added: io.DescStream
Added: io.Stream
Added: util.HasMode
Added: lang.NoCopy
Added: lang.SelfClone
Added: lang.ObjectClone
Added: lang.Unencodable
Added: lang.ObjectCodec
Result: 8 classes
Added: io.FileStream
Added: io.DescStream
Added: io.Stream
Added: util.HasMode
Added: lang.NoCopy
Added: lang.SelfClone
Added: lang.ObjectClone
Added: lang.Unencodable
Added: lang.ObjectCodec
Result: 9 classes
Added: io.PipeStream
Added: io.FileStream
Added: io.DescStream
Added: io.Stream
Added: util.HasMode
Added: lang.NoCopy
Added: lang.SelfClone
Added: lang.ObjectClone
Added: lang.Unencodable
Added: lang.ObjectCodec
Result: 10 classes
>
Each line beginning “C3:” represents a call to the algorithm. For a class with no superclasses, the algorithm just returns the class itself as the result. util.HasMode
is an example, and the result is shown on one line :-
...
C3: util.HasMode() = util.HasMode
...
For classes with superclasses, the algorithm first calls itself recursively to linearize each superclass. So for example, lang.NoCopy
has two superclasses. These are shown in parentheses as follows :-
...
C3: lang.NoCopy(lang.SelfClone, lang.Unencodable)
...
After this line the calls to the two superclasses are listed, with an extra indentation. Once those calls are complete, the results are merged together to give the result for the class itself. As each result is added by the merge, it is shown on a line by itself; finally the number of classes in the result is shown, at the same indentation as the original “C3:” line. So the output for lang.NoCopy
is :-
C3: lang.NoCopy(lang.SelfClone, lang.Unencodable)
C3: lang.SelfClone(lang.ObjectClone)
C3: lang.ObjectClone() = lang.ObjectClone
Added: lang.SelfClone
Added: lang.ObjectClone
Result: 2 classes
C3: lang.Unencodable(lang.ObjectCodec)
C3: lang.ObjectCodec() = lang.ObjectCodec
Added: lang.Unencodable
Added: lang.ObjectCodec
Result: 2 classes
Added: lang.NoCopy
Added: lang.SelfClone
Added: lang.ObjectClone
Added: lang.Unencodable
Added: lang.ObjectCodec
Result: 5 classes
At the end of the merge for PipeStream
we see that ten classes are added, and these constitute the implemented classes for PipeStream
, in that order. The fields of PipeStream
are then calculated from those classes by traversing the list. The result of that can also be shown by ieval
, as shown below :-
$ ieval -i io
> dir(PipeStream)
class io.PipeStream(io.FileStream)
at line 1077 in /home/rparlett/objecticon/lib/main/streams.icn
implements io.Stream, io.DescStream, io.FileStream, io.PipeStream, lang.ObjectClone, lang.SelfClone, lang.NoCopy, lang.ObjectCodec, lang.Unencodable, util.HasMode
adjust_mode io.FileStream 10 Method Private
ALL io.Stream 46 Public Static Const
can util.HasMode 58 Method Public
chdir io.FileStream 22 Method Public Native
close io.FileStream 25 Method Public Native Override
copy_to io.Stream 57 Method Public
create_for_fd io.FileStream 9 Method Private Static
dflag io.DescStream 33 Method Public Native
dup io.PipeStream 3 Method Public Override
dup2 io.DescStream 34 Method Public
dup2_impl io.DescStream 35 Method Private Native
dup_impl io.DescStream 36 Method Protected Native
fd io.DescStream 1 Protected
...
(omitted for brevity)
...
stderr io.FileStream 8 Public Static Const
stdin io.FileStream 6 Public Static Const
stdout io.FileStream 7 Public Static Const
tell io.FileStream 20 Method Public Native Override
TRUNCATE io.Stream 44 Public Static Const
truncate io.FileStream 21 Method Public Native Override
ttyname io.FileStream 24 Method Public Native
WRITE io.Stream 42 Public Static Const
write io.Stream 55 Method Public
write1 io.Stream 53 Method Public
writes io.Stream 54 Method Public
writes1 io.Stream 52 Method Public
wstat io.DescStream 39 Method Public Native
>
The second column in the field table gives the source class for each field.
Contents