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

Inheritance

Motivation

In the section 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 a type provides. The compiler will check if 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

drawable(x, y i32) abstract is
  draw(g graphics) abstract is
  move(dx, dy i32) is
      x := x + dx
      y := y + dy
circle(x, y, radius i32) : drawable x y is
  draw(g graphics) is
    g.draw_circle x y radius
square(x, y, side i32) : drawable x y is
  draw(g graphics) is
    g.draw_rect 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 a numeric value, that defines a total order and that can be converted 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 features that happen to have the same name. Say a feature boat has a feature max_speed, while a car also defines max_speed. If you wanted to define a feature amphibious_vehicle you would get two different features max_speed 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 will then appear only 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. Repeated 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 conflicts. If conflicting features can be renamed, using the example from above, we could have two features max_speed_on_land and max_speed_on_water.

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 meets 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 restrictive 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 my_fancy_list. Since you do not want to expose the internal datatype, stack must not inherit from my_fancy_list. Instead, stack could contain a field of type my_fancy_list.

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 conflicts 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 repeated inheritance would be renaming of the feature that was added later.