How do I implement an abstract function with multiple implementations?

← Function Extension · Ref: Q589

An abstract function can have any number of concrete implementations. Each implementation uses 'is' to extend the abstract and must match the signature exactly.

MULTIPLE IMPLEMENTATIONS

Define the abstract once, implement many times:

  MathOp as pure abstract
    -> a as Float, b as Float
    <- result as Float?
  AddOp is MathOp as pure
    -> a as Float, b as Float
    <- result as Float: a + b
  SubOp is MathOp as pure
    -> a as Float, b as Float
    <- result as Float: a - b

STRATEGY PATTERN

Store implementations in variables typed as the abstract function:

  currentOp as MathOp: AddOp
  result <- currentOp(10.0, 5.0)

Swap implementations at runtime without classes.

LIST OF FUNCTIONS

Collect implementations for iteration:

  ops <- [AddOp, SubOp, MulOp]
  for op in ops
    stdout.println(op(10.0, 5.0))

See Q588 for function extension basics. See Q51 for abstract functions. See Q214 for strategy pattern. See Q590 for dynamic function implementations.

Example

defines module qa.function.abstractimpl

  defines function

    //Abstract function: math operation contract
    MathOp as pure abstract
      ->
        operandA as Float
        operandB as Float
      <- result as Float?

    AddOp is MathOp as pure
      ->
        operandA as Float
        operandB as Float
      <- result as Float: operandA + operandB

    SubtractOp is MathOp as pure
      ->
        operandA as Float
        operandB as Float
      <- result as Float: operandA - operandB

    MultiplyOp is MathOp as pure
      ->
        operandA as Float
        operandB as Float
      <- result as Float: operandA * operandB

  defines program

    AbstractFunctionImplDemo()
      stdout <- Stdout()

      //Direct calls
      stdout.println(`Add: ${AddOp(10.0, 3.0)}`)
      stdout.println(`Sub: ${SubtractOp(10.0, 3.0)}`)
      stdout.println(`Mul: ${MultiplyOp(10.0, 3.0)}`)

      //Strategy pattern: swap at runtime
      currentOp as MathOp: AddOp
      stdout.println(`Current (add): ${currentOp(5.0, 2.0)}`)

      currentOp: MultiplyOp
      stdout.println(`Current (mul): ${currentOp(5.0, 2.0)}`)

      //List of operations
      operations <- List() of MathOp
      operations += AddOp
      operations += SubtractOp
      operations += MultiplyOp

      for operation in operations
        stdout.println(`Result: ${operation(8.0, 4.0)}`)

Common mistakes

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

Incorrect:

AddOp is MathOp as pure
      ->
        operandA as Float
        operandB as Float
      <- result as Float: operandA + operandB
      stdout.println(result)

Correct:

AddOp is MathOp as pure
      ->
        operandA as Float
        operandB as Float
      <- result as Float: operandA + operandB
Other ways to ask this
  • How do I create multiple function implementations?
  • Can I have several functions extending the same abstract?
  • How do I use abstract functions for strategy pattern?

Coming from another language?

Java: strategy pattern requires interface + multiple classes. Python: pass different functions directly (no type checking). Rust: closures with Box<dyn Fn>. Kotlin: functional types + lambda. EK9: abstract function + multiple 'is' implementations, true function polymorphism.

Keywords: swap, handler, visitor, polymorphism, list, extend, multiple, function, abstract, implementation, strategy, sealed