Can functions form multi-level inheritance chains?

← Function Extension · Ref: Q593

EK9 functions can form multi-level chains: an abstract function at the top, with concrete implementations at each level. However, only abstract functions can be extended. A concrete function cannot be further extended.

ABSTRACT FUNCTION EXTENSION

Abstract functions define contracts that concrete functions implement:

  BaseOp as pure abstract
    -> x as Integer
    <- rtn as Integer?
  DoubleOp is BaseOp as pure
    -> x as Integer
    <- rtn as Integer: x + x

CONCRETE FUNCTIONS ARE CLOSED

Once a function provides an implementation (has a body), it cannot be extended further. Only abstract functions can be the base of a hierarchy.

MULTIPLE IMPLEMENTATIONS

An abstract function can have many direct implementations, creating a flat hierarchy with one abstract parent and multiple concrete children.

See Q588 for function extension basics. See Q589 for multiple implementations. See Q594 for extension rules. See Q595 for pure abstract function constraints.

Example

defines module qa.function.multilevelchain

  defines constant

    MIN_FILTER_LENGTH <- 3

  defines function

    //Abstract function: base of hierarchy
    Filter as pure abstract
      -> input as String
      <- passes as Boolean?

    //Multiple direct implementations (flat hierarchy)
    NonEmptyFilter is Filter as pure
      -> input as String
      <- passes as Boolean: length input > 0

    LongEnoughFilter is Filter as pure
      -> input as String
      <- passes as Boolean: length input > MIN_FILTER_LENGTH

    ContainsAtFilter is Filter as pure
      -> input as String
      <- passes as Boolean: input contains "@"

  defines program

    MultilevelFunctionChainDemo()
      stdout <- Stdout()

      //All implementations share the abstract type
      filters <- List() of Filter
      filters += NonEmptyFilter
      filters += LongEnoughFilter
      filters += ContainsAtFilter

      testValues <- ["", "hi", "hello", "user@host"]

      for testVal in testValues
        stdout.println(`Testing: "${testVal}"`)
        for filterFn in filters
          stdout.println(`  Passes: ${filterFn(testVal)}`)

Common mistakes

E50001 — A pure function cannot call non-pure methods like stdout.println(). Pure functions must have no side effects. See ek9 -h E50001 for details.

Incorrect:

NonEmptyFilter is Filter as pure
      -> input as String
      <- passes as Boolean: length input > 0
      stdout.println(input)

Correct:

NonEmptyFilter is Filter as pure
      -> input as String
      <- passes as Boolean: length input > 0
Other ways to ask this
  • Can a concrete function be extended further?
  • How deep can function hierarchies go?
  • Can I have Abstract -> Middle -> Concrete function chains?

Coming from another language?

Java: functional interfaces are flat (one method). Kotlin: function types are structural. Rust: Fn traits have no hierarchy. EK9: abstract functions can have multiple concrete implementations, concrete functions are closed.

Keywords: extend, function, multi-level, extension, chain, concrete, closed, abstract, hierarchy