How does method dispatch work in EK9?

← Classes and OOP · Ref: Q105

EK9 supports method dispatch via the 'as dispatcher' keyword. A dispatcher method acts as an entry point that automatically routes to the most specific overloaded method based on the runtime type of arguments.

BASIC DISPATCH

Normal method calls use compile-time types. When you need runtime type dispatch, mark the base method with 'as dispatcher':

  render() as dispatcher
    -> shape as Shape
    <- rtn as String: shape.draw()

The runtime selects the most specific 'render' overload. The dispatcher method is the fallback when no specific handler matches.

OVERLOADED HANDLERS

Overloaded methods handle specific types:

  render()
    -> c as Circle
    <- rtn as String: "circle: " + c.draw()
  render()
    -> r as Rectangle
    <- rtn as String: "rectangle: " + r.draw()

COMPILE-TIME VALIDATION

The compiler validates dispatchers extensively:

  E05180 — private method in super has same name as dispatcher (won't participate in dispatch)
  E05210 — handler type not in base type hierarchy (e.g. Integer handler in Shape dispatcher)
  E05260 — dispatcher on sealed type missing handlers for permitted types
  E05220 — handler return type incompatible with dispatcher return type
  E06320 — handler parameter count differs from dispatcher
  E05170 — purity mismatch (dispatcher pure but handler not pure)
  E07820 — multiple methods marked 'as dispatcher' with same name

SEALED TYPES WITH DISPATCHERS

When a trait uses 'allow only', the compiler enforces exhaustive dispatch:

  Shape allow only Circle, Square, Triangle

A dispatcher on Shape must have handlers for ALL permitted types (Circle, Square, AND Triangle) or E05260 is triggered. This eliminates the missing-case bugs that plague Visitor patterns.

DOUBLE DISPATCH

Dispatchers can take two arguments:

  intersect() as dispatcher
    -> s1 as Shape, s2 as Shape

With overloads for specific type pairs, the runtime picks the best match. Each parameter is independently validated against its base type's hierarchy.

WHEN TO USE DISPATCHER

Use dispatchers when you need visitor-like patterns without the visitor boilerplate. They replace Java's instanceof chains, C#'s type switches, and the full Visitor pattern — with compile-time safety guarantees.

See Q96 for operators. See Q60 for function dispatching. See Q106 for traits. See Q107 for multiple traits. See Q211 for double dispatch pattern. See Q298 for sealed traits. See Q612-Q621 for dispatcher validation Q&As.

Example

defines module qa.oop.dispatch

  defines trait

    <?-
      Sealed trait: only Circle, Square, Triangle can implement it.
      Dispatchers on Shape must handle ALL three types (E05260).
    -?>
    Shape allow only Circle, Square, Triangle
      area() as abstract
        <- rtn as Float?

  defines class

    Circle with trait of Shape
      override area()
        <- rtn as Float: 3.14
      default operator ?

    Square with trait of Shape
      override area()
        <- rtn as Float: 1.0
      default operator ?

    Triangle with trait of Shape
      override area()
        <- rtn as Float: 0.5
      default operator ?

    <?-
      Renderer dispatches on Shape.
      Because Shape is sealed, ALL permitted types must have handlers.
    -?>
    Renderer

      render() as dispatcher
        -> shape as Shape
        <- rtn as String: `unknown shape area: ${shape.area()}`

      render()
        -> circle as Circle
        <- rtn as String: `circle area: ${circle.area()}`

      render()
        -> square as Square
        <- rtn as String: `square area: ${square.area()}`

      render()
        -> triangle as Triangle
        <- rtn as String: `triangle area: ${triangle.area()}`

  defines program

    DispatchDemo()
      stdout <- Stdout()

      renderer <- Renderer()

      // === DISPATCHER: routes to most specific overload ===

      shapes <- List() of Shape
      shapes += Circle()
      shapes += Square()
      shapes += Triangle()

      for shape in shapes
        stdout.println(renderer.render(shape))

      // === Direct calls also work ===

      stdout.println(renderer.render(Circle()))
      stdout.println(renderer.render(Triangle()))

Common mistakes

E05120 — When implementing an abstract method from Shape in a subclass like Circle, the 'override' keyword is mandatory. Omitting it triggers E05120. See ek9 -h E05120 for details.

Incorrect:

area()

Correct:

override area()

E50060 — A dispatcher handler's parameter type must be in the base dispatch type's hierarchy. An Integer handler in a Shape dispatcher can never be reached because Integer is not a subtype of Shape. This triggers E50060. See ek9 -h E50060 for details.

Incorrect:

-> circle as Integer

Correct:

-> circle as Circle

E05030 — Renderer is closed by default (not marked 'as open'). Attempting to extend a closed class triggers E05030. Only traits and classes marked 'as open' or 'as abstract' can be extended. See ek9 -h E05030 for details.

Incorrect:

Circle extends Renderer

Correct:

Circle with trait of Shape

E07075 — Traits are stateless behaviour contracts — they cannot have properties or fields. The Shape trait should define abstract methods (like area()), not hold state. Move fields to implementing classes. See ek9 -h E07075 for details.

Incorrect:

cachedArea <- 0.0

Correct:

area() as abstract
        <- rtn as Float?
Other ways to ask this
  • What is the dispatcher keyword in EK9?
  • How does double dispatch work in EK9?
  • How do I dispatch on runtime types in EK9?
  • How does polymorphism work in EK9?

Coming from another language?

Java: Visitor pattern with accept/visit boilerplate, or instanceof chains. Python: functools.singledispatch for single argument, no multi-dispatch built-in. Rust: no runtime dispatch, pattern matching on enums instead. Go: type switch for runtime dispatch, single argument only. Kotlin: Visitor pattern or when-is chains, no built-in multi-dispatch. EK9: 'as dispatcher' keyword enables automatic runtime dispatch to most specific overload, supports multiple arguments for double dispatch, no visitor boilerplate.

Keywords: handler, double, polymorphism, visitor, overload, only, purity, method, dispatcher, type, allow, object-oriented, sealed, exhaustive, specific, runtime, pattern, hierarchy, dispatch