How does abstract implementation flow through a three-level hierarchy?

← Type Hierarchy Constraints · Ref: Q677

Note: override applies to both methods and operators (e.g., override operator ? as pure, override operator $ as pure).

EK9 supports multi-level abstract hierarchies where abstract classes can extend other abstract classes, deferring implementation to concrete leaf classes. Each level can implement some abstract methods and declare new ones.

ABSTRACT EXTENDING ABSTRACT

An abstract class can extend another abstract class. The intermediate abstract class may implement some inherited abstract methods while leaving others for descendants.

CONCRETE LEAF REQUIREMENT

The concrete (non-abstract) class at the bottom of the chain MUST implement ALL remaining abstract methods from every ancestor. Missing implementations trigger compile errors.

OPEN MODIFIER REQUIREMENT (E05020)

For a class to be extended, it must be declared 'as open' or 'as abstract'. Abstract implies open. A class not declared as open or abstract cannot be extended.

CORRECT PATTERN

  Shape as abstract           // Level 1: pure abstract
    area() as abstract
  Polygon extends Shape as abstract  // Level 2: still abstract
    sides() as abstract
    override area()            // Implements area
  Triangle extends Polygon     // Level 3: concrete, implements sides()
    override sides()

See Q602 for circular hierarchy detection. See Q605 for this-context rules. See Q567 for purity chain inheritance. See Q606 for method shadowing.

Example

defines module qa.typehierarchy.abstractchain

  defines class

    <?-
      Level 1: Pure abstract base.
      Declares the fundamental operations.
    -?>
    Shape as abstract

      area() as pure abstract
        <- rtn as Float?

      perimeter() as pure abstract
        <- rtn as Float?

      describe() as pure abstract
        <- rtn as String?

      default operator ?

    <?-
      Level 2: Abstract intermediate.
      Implements describe(), leaves area() and perimeter() abstract.
    -?>
    Polygon extends Shape as abstract

      sides() as pure abstract
        <- rtn as Integer?

      override describe() as pure
        <- rtn as String: "polygon"

      default operator ?

    <?-
      Level 3: Concrete leaf.
      Must implement ALL remaining abstract methods.
    -?>
    Rectangle extends Polygon
      width <- Float()
      height <- Float()

      Rectangle()
        ->
          width as Float
          height as Float
        this.width: width
        this.height: height

      override area() as pure
        <- rtn as Float: width * height

      override perimeter() as pure
        <- rtn as Float: (width + height) * 2.0

      override sides() as pure
        <- rtn as Integer: 4

      override describe() as pure
        <- rtn as String: "rectangle"

      default operator ?

    <?-
      Another concrete leaf from the same hierarchy.
    -?>
    EquilateralTriangle extends Polygon
      sideLength <- Float()

      EquilateralTriangle()
        -> sideLength as Float
        this.sideLength: sideLength

      override area() as pure
        <- rtn as Float: (sideLength * sideLength * 0.433)

      override perimeter() as pure
        <- rtn as Float: sideLength * 3.0

      override sides() as pure
        <- rtn as Integer: 3

      override describe() as pure
        <- rtn as String: "equilateral triangle"

      default operator ?

    <?-
      A concrete, non-abstract pair. Panel may extend Widget only while Widget is
      'as open'; drop 'as open' and Widget is closed, so the extension is rejected.
    -?>
    Widget as open
      label <- "widget"

      default operator ?

    Panel extends Widget
      visible <- true

      default operator ?

  defines program

    AbstractChainDemo()
      stdout <- Stdout()

      shapes <- List() of Shape
      shapes += Rectangle(4.0, 3.0)
      shapes += EquilateralTriangle(5.0)

      for shape in shapes
        stdout.println(`${shape.describe()}: area=${shape.area()}`)

Common mistakes

E05030 — EK9 classes are closed by default. Panel can extend Widget only because Widget is declared 'as open'. Removing 'as open' makes Widget closed, so 'Panel extends Widget' triggers E05030 - not open to be extended. See ek9 -h E05030 for details.

Incorrect:

Widget

Correct:

Widget as open

E05110 — A class can only extend a compatible type. Attempting to extend a built-in or incompatible type triggers E05110 because the target type is not valid in this context. See ek9 -h E05110 for details.

Incorrect:

Rectangle extends String

Correct:

Rectangle extends Polygon

E05150 — When overriding a pure abstract method, the override must also be declared pure. Removing purity from the override creates a contract mismatch and triggers E05150. See ek9 -h E05150 for details.

Incorrect:

override area()
        <- rtn as Float: width * height

Correct:

override area() as pure
        <- rtn as Float: width * height
Other ways to ask this
  • What is E05070 incorrect use of this in context?
  • What is E05020 circular hierarchy detection?
  • What is E05150 purity mismatch in override?
  • How do abstract intermediate classes work in EK9?

Coming from another language?

Java: abstract class extending abstract class is standard. Kotlin: abstract classes extend abstract classes. Python: ABC can extend ABC. Rust: no inheritance, use trait composition. Go: interface embedding instead. EK9: abstract extends abstract with 'as abstract' or 'as open', concrete leaf must implement all remaining abstract methods.

Keywords: type, inherit, concrete, circular, E05150, open, leaf, abstract, implementation, E05020, hierarchy, E05070, chain