How do function hierarchies compare to class hierarchies?

← Function Extension · Ref: Q591

Both function hierarchies and class hierarchies provide polymorphism in EK9, but they serve different purposes. Functions are best for stateless transformations; classes are best for stateful entities.

FUNCTION HIERARCHIES

Abstract function defines a callable contract. Concrete functions implement it. No state (unless using closure capture). Best for: transformations, strategies, pipelines, predicates.

CLASS HIERARCHIES

Abstract class defines entity with state and behavior. Concrete classes extend it. Full state management with fields and methods. Best for: domain entities, stateful components, complex objects.

WHEN TO USE FUNCTIONS

Single input-output transformations. Strategy pattern with no shared state. Stream pipeline stages (filter, map, etc.). Predicates and validators.

WHEN TO USE CLASSES

Domain objects with multiple fields. Objects with multiple methods. Stateful services and components. Complex object graphs.

KEY DIFFERENCES

Functions cannot hold mutable state (closures capture values, not variables). Classes can have fields, methods, and operators. Functions are more concise for simple transformations. Classes provide richer abstractions.

See Q588 for function extension. See Q103 for abstract classes. See Q212 for composition over inheritance. See Q214 for strategy pattern.

Example

defines module qa.function.vsclass

  defines constant

    MIN_VALIDATION_LENGTH <- 2

  defines function

    //Function hierarchy: stateless transformation
    Validator as pure abstract
      -> input as String
      <- isValid as Boolean?

    LengthValidator is Validator as pure
      -> input as String
      <- isValid as Boolean: length input > MIN_VALIDATION_LENGTH

    PatternValidator is Validator as pure
      -> input as String
      <- isValid as Boolean: input contains "@"

  defines class

    //Class hierarchy: stateful entity
    Document as abstract
      title <- String()

      Document()
        -> title as String
        this.title: title

      title() as pure
        <- rtn as String: title

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

      default operator ?

    TextDocument extends Document
      content <- String()

      TextDocument()
        ->
          title as String
          content as String
        super(title)
        this.content: content

      override wordCount() as pure
        <- rtn as Integer: length content

      default operator ?

  defines program

    FunctionVsClassHierarchyDemo()
      stdout <- Stdout()

      //Function hierarchy: simple validation
      validators <- List() of Validator
      validators += LengthValidator
      validators += PatternValidator

      testInput <- "a@b"
      for validator in validators
        stdout.println(`Valid: ${validator(testInput)}`)

      //Class hierarchy: rich entity
      doc <- TextDocument("Report", "This is the content of the report")
      stdout.println(`${doc.title()}: ${doc.wordCount()} chars`)

Common mistakes

E05050 — The super() call must be the very first statement in a child class constructor. No code can execute before the parent is initialized. See ek9 -h E05050 for details.

Incorrect:

TextDocument()
        ->
          title as String
          content as String
        this.content: content
        super(title)

Correct:

TextDocument()
        ->
          title as String
          content as String
        super(title)
        this.content: content

E05120 — When overriding an abstract method from a parent class, the 'override' keyword is required. Omitting it triggers a missing override error. See ek9 -h E05120 for details.

Incorrect:

wordCount() as pure
        <- rtn as Integer: length content

Correct:

override wordCount() as pure
        <- rtn as Integer: length content
Other ways to ask this
  • Should I use function extension or class inheritance?
  • What is the difference between function and class hierarchies?
  • When should I prefer functions over classes for polymorphism?

Coming from another language?

Java: interfaces for behavior, classes for state. Python: first-class functions but no type hierarchy. Rust: Fn traits for functions, structs for state. Go: interfaces for behavior, structs for state. EK9: both functions and classes are nominal types with inheritance hierarchies.

Keywords: stateless, function, polymorphism, comparison, visitor, extend, class, when, abstract, handler, sealed, state, hierarchy