What are abstract functions in EK9?

← Getting Started · Ref: Q51

Abstract functions in EK9 are STANDALONE TYPES that define a callable contract. This is unique to EK9 — in every other mainstream language, abstract behaviour requires classes or interfaces. In EK9, a function can be abstract on its own:

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

This declares a TYPE called 'mathOperation'. Any function that says 'is mathOperation' or 'extends mathOperation' becomes a concrete implementation of that type.

IMPLEMENTING WITH 'is' OR 'extends'
Named functions implement an abstract function using 'is' or 'extends' (both are equivalent for functions):

  addOp() is mathOperation as pure
    -> x as Float, y as Float
    <- result as Float: x + y
  subtractOp() is mathOperation as pure
    -> x as Float, y as Float
    <- result as Float: x - y

FUNCTION POLYMORPHISM

Because abstract functions are types, you get true polymorphism. Store implementations in a list and iterate:

  for op in [addOp, subtractOp, multiplyOp]
    stdout.println(`Result: ${op(10.0, 3.0)}`)

This is polymorphic dispatch — each function in the list is called through the same abstract type. No interfaces, no classes, no boilerplate.

FUNCTION DELEGATES

Variables can hold function references via their abstract type:

  currentOp as mathOperation: addOp
  result <- currentOp(5.0, 3.0)
  currentOp: subtractOp
  result2 <- currentOp(5.0, 3.0)

Without parentheses, 'addOp' is a REFERENCE (delegate). With parentheses, 'addOp(5.0, 3.0)' is a CALL. This distinction is fundamental.

Why is this more powerful than other languages? In Java, you need a functional interface (a class-like construct) to achieve the same. In Python, functions are first-class but have no type contract. In Rust, closures use Fn traits (structural) but you cannot name closure types directly. In Go, function types exist but cannot form inheritance hierarchies. EK9 abstract functions give you type safety, polymorphism, and named types — all without classes.

See Q52 for dynamic functions that implement abstract functions inline. See Q55 for passing functions as delegates. See Q56 for higher-order functions that return delegates. See Q59 for using function delegates in stream pipelines. See Q89 for how abstract functions serve as stream pipeline stage types. See Q103 for abstract classes and methods. See Q235 for complete stream operations reference. See Q588 for function extension and type hierarchies. See Q589 for abstract function implementation patterns.

Example

defines module qa.abstractfunction

  defines function

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

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

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

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

  defines program

    AbstractFunctionDemo()
      stdout <- Stdout()

      // Polymorphic dispatch — iterate over implementations
      ops <- [addOp, subtractOp, multiplyOp]
      for op in ops
        stdout.println(`op(10.0, 3.0) = ${op(10.0, 3.0)}`)

      // Function delegate — variable holds a reference
      currentOp as mathOperation: addOp
      stdout.println(`delegate: ${currentOp(5.0, 3.0)}`)

      // Reassign to different implementation
      currentOp: multiplyOp
      stdout.println(`reassigned: ${currentOp(5.0, 3.0)}`)

Common mistakes

E05030 — Functions are closed by default. Extending a non-abstract, non-open function triggers E05030 — not open to be extended. Only abstract or open functions can be extended. See ek9 -h E05030 for details.

Incorrect:

addOp() is addOp as pure

Correct:

addOp() is mathOperation as pure
Other ways to ask this
  • How do abstract functions work as standalone types in EK9?
  • How do I implement an abstract function in EK9?
  • How do I use function polymorphism in EK9?

Coming from another language?

Java: requires functional interfaces (SAM types) like Predicate<T> or Function<T,R> which are class-like constructs, not standalone function types, @FunctionalInterface annotation is documentation only. Python: no abstract function concept, abc.abstractmethod requires a class wrapper, functions have no type contract. JavaScript: no abstract functions, no type system for functions, TypeScript has callable interfaces but they are structural. Rust: Fn/FnMut/FnOnce traits are structural not nominal, cannot name closure types without impl Trait or dyn Trait, no standalone abstract function type. Go: type aliases for function signatures (type MathOp func(float64, float64) float64) but no inheritance hierarchy, no polymorphic dispatch. C#: delegates define callable types but require separate delegate declarations, not function inheritance. Kotlin: functional types are structural ((Int, Int) -> Int), fun interface for SAM but still requires class-like construct. Swift: closures are structural, protocol-based approach needed for nominal typing. EK9: abstract functions ARE standalone types with nominal identity, implementations use 'is' or 'extends' for true function type hierarchies, polymorphic dispatch through abstract function variables, no class wrapper needed.

Keywords: beginner, start, visitor, reference, type, abstract, is, polymorphism, delegate, standalone, implement, handler, extends, intro, sealed, contract, first, function, migrate, call