Inheritance in EK9

There have been several examples of inheritance of various constructs in several of the other sections. See the list of examples for details.

This section explains the use inheritance and also why other mechanisms are sometimes recommended. The concept of inheritance is an Object-Oriented one; EK9 uses this same concept but with constructs other than just classes. It is the valuable concept of polymorphism that is the vital aspect we are looking to employ; irrespective of the construct.

Constructs that support inheritance

EK9 has a number of constructs that are suitable for particular tasks, many of these support inheritance.

Rationale

EK9 does have a wide range of constructs; whereas many languages focus on classes or maybe just functions as the primary structural construct. Each of the constructs in EK9 is designed to provide functionality in a specific role. This means that capabilities such as inheritance/composition are used in a very specific manner and for some constructs must be limited (or enhanced).

While it is true to say the mechanisms of abstraction, encapsulation and inheritance are important; polymorphism is really is the main power behind Object-Oriented Programming. EK9 applies polymorphism to several constructs in addition to classes; this widens its use.

EK9 supports multiple inheritance of traits. This is to facilitate the development of Façades and enable a single class to support both inheritance and automatic delegation to other classes.
Traits are all implicitly abstract and can therefore be extended. Take care here not to use multiple inheritance of traits to build a Façade with too many methods (and coupling). You may find that using components provides a suitable alternative and enables you to compose various implementations and hide the implementation details but expose just enough via the component.

Functions can also treated as Objects and can be abstract; this enables them to be used as function delegates; hence they may be used in an interchangeable manner in stream pipelines. They can actually be piped through pipelines and called (asynchronously if required). But importantly they are type safe and can be type checked. Only by having the concept of an abstract function could the actual types of functions being used, altered and varied in a type safe manner.

Functions and dynamic functions are not lambdas - they are similar - but they are not (and are not designed to be) true lambdas.

Classes, records and components only support single inheritance, but classes can also exhibit multiple traits.

Denoting components as a specific construct that support an abstract base, enables components can be injected (see dependency injection). This is not the case with classes or records they cannot be 'injected'.

Different constraints and capabilities on the constructs with respect to inheritance enable each construct to fulfil the role it is designed for, rather than attempting to force the class to fit every role.

Those with and Object-Oriented background will probably feel at home with traits and classes. Those that have used Spring will probably see where components can fit in. Developers with a more functional background will look to functions/records (and abstract functions) with stream pipelines as their main focus.

It is not necessary to use every type of construct in a solution; only use the features and capabilities you need or feel most at home with. For example C programmers may find just using program, function and record feels most natural to start with.

With EK9, there are a number of tools (constructs) that are available; adopting a pure Functional, total Object-Oriented or a hybrid approach to development is your choice. For different projects you will probably alter the balance of the range of constructs used.

Extending Functionality

Only if a construct is open or abstract can it be extended but see traits as these can provide more fine-grained control.

Overriding Methods

When looking to alter a methods functionality in classes, traits or components; the keyword override must be used. This is covered in more detail in the methods section. Actually the same applies to operators on records, classes and traits. If you are familiar with Kotlin/Java and have used the 'Annotations' such as @Override then this concept will be familiar to you.

Additional Examples

Records

The record example shows additional properties being added to records, but also uses a wide range of operators including overriding operators.

Functions

Below is an additional example of abstract functions; this highlights how functions can be treated in an Object like manner. Also note this approach is SOLID even though we are dealing with functions and not classes.

#!ek9
defines module introduction
  defines function

    mathOperation() as pure abstract
      ->
        x as Float
        y as Float
      <-
        result as Float?

    add() is mathOperation as pure
      ->
        x as Float
        y as Float
      <-
        result as Float: x + y

    subtract() is mathOperation as pure
      ->
        x as Float
        y as Float
      <-
        result as Float: x - y

    divide() is mathOperation as pure
      ->
        x as Float
        y as Float
      <-
        result as Float: x / y

    multiply() is mathOperation as pure
      ->
        x as Float
        y as Float
      <-
        result as Float: x * y

  defines program

    MathExample()
      stdout <- Stdout()
      stdout.println("Math Operation Example")

      for op in [add, subtract, divide, multiply]
        stdout.println(`Result: ${op(21, 7)}`)

//EOF

The above example demonstrates how the arithmetic functions can be defined to meet the same signature (mathOperation); and can be treated in a polymorphic manner. The loop in the 'MathExample' program can call each of the functions without having to be specific as to which function they are. But critically this is type safe and exhibits polymorphism.

There are a couple of interesting points to note in this example above:

One could imagine this type of dynamic capability being used in a wide range of applications that triggers varying functionality in specific circumstances (UI button clicks, for example).

Classes

This class example shows several classes being extended, some are open for further extension. This second class example shows classes with specific traits. This final example shows how class inheritance can actually be avoided through the use of composition.

Traits

This example shows traits being used for multiple inheritance and also controlling/limiting how classes can inherit from bases.

Summary

Inheritance is useful in many contexts; EK9 has attempted to limit inheritance in some scenarios and enhance it in others. It has also provided an alternative approach to extending and reusing functionality through composition.

Next Steps

Back to functions, specifically Dynamic Functions; these follow the same thread of inheritance (but inheritance from abstract functions delivering polymorphism).