What are the rules for when a function can or cannot be extended?

← Function Extension · Ref: Q594

Functions are closed by default. An abstract function can always be extended; a concrete function (one with an implementation body) can be extended only when it is declared 'as open'. Extending a closed function is error E05030, not open to be extended.

CAN EXTEND: ABSTRACT FUNCTIONS

Functions declared with 'as abstract' can be extended:

  defines function
    TextCheck as pure abstract
      -> item as String
      <- result as Boolean?

CLOSED BY DEFAULT: CONCRETE FUNCTIONS

Functions with a body cannot be extended unless they are declared 'as open':

  defines function
    addOne() as pure
      -> x as Integer
      <- rtn as Integer: x + 1
    //Cannot write: DoubleAddOne is addOne - E05030, not open to be extended

SIGNATURE MUST MATCH (E05140)

The extending function must match the abstract function's parameter types, count, and return type. Mismatches produce E05140.

PURITY MUST BE COMPATIBLE

If the abstract function is pure, implementations must be pure (same E05150 rule as classes). If the abstract function is not pure, implementations must not be pure either (E05160).

See Q588 for function extension basics. See Q592 for signature mismatch. See Q593 for multi-level chains. See Q595 for pure abstract function constraints.

Example

defines module qa.function.extensionrules

  defines function

    //Abstract: CAN be extended
    Converter as pure abstract
      -> input as Integer
      <- output as String?

    //Implementation 1
    DecimalConverter is Converter as pure
      -> input as Integer
      <- output as String: $input

    //Implementation 2
    PrefixedConverter is Converter as pure
      -> input as Integer
      <- output as String: "0x" + $input

    //Concrete function: closed by default, so it CANNOT be extended
    doubleIt() as pure
      -> number as Integer
      <- result as Integer: number + number

  defines program

    FunctionExtensionRulesDemo()
      stdout <- Stdout()

      //Abstract function implementations
      converters <- List() of Converter
      converters += DecimalConverter
      converters += PrefixedConverter

      for converter in converters
        stdout.println(converter(255))

      //Concrete function: used directly
      stdout.println(`Double 5: ${doubleIt(5)}`)

Common mistakes

E08120 — A function declared as pure must not mutate variables. The += operator mutates number in place, so the compiler reports E08120. See ek9 -h E08120 for details.

Incorrect:

doubleIt() as pure
      -> number as Integer
      <- result as Integer: number + number
      number += number

Correct:

doubleIt() as pure
      -> number as Integer
      <- result as Integer: number + number
Other ways to ask this
  • When can I extend a function in EK9?
  • What functions can be used as a base type?
  • Why can't I extend a concrete function?

Coming from another language?

Java: functional interfaces can only have one abstract method. Kotlin: function types are structural, no extension. Rust: no function type inheritance. EK9: abstract functions can be extended, concrete functions are closed unless declared as open (E05030), E05140 for signature mismatch.

Keywords: extension, function, extend, E05150, purity, immutable, closed, concrete, rules, side-effect, abstract, E05140