Fuzion Logo
flang.dev — The Fuzion Language Portal
JavaScript seems to be disabled. Functionality is limited.

Inheritance

Motivation

In the section [[fuzion_choice_type|Fuzion Choice Type]], one of Fuzion's mechanisms to store data of different types has been presented. Choice types are ideal for situations in which the number of possible choices is known and fixed from the beginning, while new operations on that data might be added later with little effort. Any new operation has to provide code to handle all the choices that type provides, the compiler will check the code is complete.

However, in situations where the kind of operations on data are known and fixed, but the actual kind of data might change and new data might be added, the choice types provide little help.

One classic example is a graphical editor that supports an extensible set of graphical objects such as boxes, circles, text blocks, polygons, images, etc. A small set of operations must be performed on these objects, e.g., drawing onto the screen, scaling the object.

For this purpose, object-oriented techniques such as classes with inheritance and dynamic binding are ideal. However, these concepts require additional data for type information and additional code execution for call resolution. This is why many purely object-oriented languages suffer from a performance penalty.

Fuzion is a pure object oriented language in the sense that every feature defines a type that can be used for inheritance and dynamic binding. However, fuzion does not require type information or dynamic binding in cases that are not polymorphic. Furthermore, the compiler specializes code to avoid this overhead in many of the remaining cases.

Example code

abstract Drawable(var i32 x, y) {
  abstract draw(Graphics g; i32 x, y);
  move(i32 dx, dy)
    {
      x = x + dx;
      y = y + dy;
    }
}
Circle(i32 x, y, radius) : Drawable(x, y) {
  draw(Graphics g) { g.drawCircle(x, y, radius); }
}
Square(i32 x, y, side) : Drawable(x, y) {
  draw(Graphics g) { g.drawRect(x, y, x+side, y+side); }
}

Multiple Inheritance

Fuzion supports multiple inheritance, such that different aspects of a feature can be expressed via inheritance. An example is an numeric value, that defines a total order and that can be converte to a string. Consequently, the feature defining the numeric value should inherit from features like comparable and printable.

Inheritance Conflicts

The presence of multiple inheritance leads to several possible conflicts, that need to be resolved:

Name Conflicts

A conflicting inheritance occurs when a feature inherits from two different parents two different features that happen to have the same name. Say a feature boat has a feature maxSpeed, while a car also defines maxSpeed. If you wanted to define a feature you would get two different features maxSpeed with different meanings.

Repeated Inheritance

Repeated inheritance happens when the same feature is inherited through different parents. This is no problem if the feature is not redefined on the way, the original feature then will appear once in the heir.

However, if a feature is redefined on one or several of the inheritance paths, we end up with conflicting implementations that need to be resolved somehow.

Conflicting Generics

Through inheritance, a feature could inherit repeatedly from a generic parent giving different actual generic arguments.

Invisible Conflict

A conflict might occur between features that are not visible by the heir class. For example, a module might export a feature that redefines a feature that is visible only locally within the module. Ŕepeated inheritance in another module could then result in conflicts that cannot be predicted by the developer of the other module.

Resolving Conflicts

Renaming

A mechanism to rename features that are inherited helps to solve name conficts: Conflicting features can be renamed, using the example from above, we could have two features maxSpeedOnLand and maxpeedOnWater.

TBD: In case one feature f defined in feature A is inherited repeatedly through B and C and renamed as f1 and f2 in the heir class D, which version should be called in a call a.f with the target having static type A? Possible options: explicit selecting one renamed version or just using the first inheritance clause.

Redefinition

In case of conflicting implementations, the heir class could provide a new implementation that meats the requirements of both implementations that were inherited.

Handling Generics

A simple solution for conflicting generics would be to just forbid repeated inheritance unless the generic arguments are equal.

Alternatively, generic classes with different actual generic arguments could be treated like completely different classes. Then, the repeated inheritance results in name conflicts that could be resolved by renaming.

Visibility

One solution to avoid invisible conflicts would be to forbid inheriting from features and redefining inherited features that have a visibility that is more restricted than the heir class' visibility. I.e., a feature exported from a module may only inherit from features that are also exported from the module and may only redefine features that are also exported.

One consequence this has is that inheritance cannot be hidden, inheritance is not an implementation detail. Consequently, inheritance should not be used as a means of implementation of a feature unless this implementation should be revealed to the public.

As an example, say you implement a module that provides basic data types like stack based on an internal datatype myFancyList. Since you do not want to expose the internal datatype, stack must not inherit from myFancyList. Instead, stack could contain a field type myFancyList.

Late Inheritance

It should be possible to add inheritance relations to existing features from other modules as long as these new inheritance relations are invisible to that other module. This is similar to adding the implementation of a trait to a type in Rust.

This could result in name conficts and repeated inheritance. Name conflicts can be solved by renaming within the heir module.

To solve repeated inheritance, redefining external features is not an option: apart from being very error-prone it would result in new conflicts when there are conflicting redefinitions in different modules. Instead, the only permissible way to solve repated inheritance would be renaming of the feature that was added later.