How does method overloading work compared to function dispatching?

← Functions and Methods · Ref: Q600

EK9 has two distinct mechanisms for calling different code based on types: method overloading (compile-time resolution) and dispatching (runtime resolution).

METHOD OVERLOADING

Classes can have multiple methods with the same name but different parameter types. The compiler resolves which overload to call at COMPILE time using cost-based matching: exact type match (cost 0), subtype (cost 1), promotion like Integer to Float (cost 2), and Any match (cost 3). The lowest cost wins. If two overloads tie, the compiler reports an ambiguity error.

DISPATCHER KEYWORD

The 'dispatcher' keyword enables RUNTIME type-based dispatch. Mark a method as dispatcher and provide overloaded implementations:

  format() as dispatcher
    -> item as Any
    <- rtn as String: $item
  format()
    -> item as Integer
    <- rtn as String: "Int: " + $item

When called, the runtime checks the actual type of the argument and dispatches to the most specific overload.

KEY DIFFERENCE

With overloading, if you have a variable typed as 'Any' that holds an Integer, the compiler selects the 'Any' overload. With dispatcher, the runtime selects the 'Integer' overload because it examines the actual type.

FUNCTION DISPATCHING

Standalone functions cannot share names in the same module. For function-level dispatch, use the 'as dispatcher' modifier. Dispatchers are limited to one or two parameters, must have the same parameter count across overloads, and must share the same purity.

WHEN TO CHOOSE

Use method overloading when the caller knows the exact type at compile time. Use dispatcher when processing heterogeneous collections or when the runtime type matters. Dispatcher eliminates the need for the visitor pattern.

See Q60 for function dispatching basics. See Q105 for method dispatch in class hierarchies. See Q255 for cost-based method resolution details. See Q596 for function vs method distinction.

Example

defines module qa.functionsAndMethods.overloadingVsDispatch

  defines class

    //Class with overloaded methods (compile-time resolution)
    Formatter
      formatItem() as pure
        -> item as String
        <- rtn as String: "String: " + item

      formatItem() as pure
        -> item as Integer
        <- rtn as String: "Integer: " + $item

      formatItem() as pure
        -> item as Float
        <- rtn as String: "Float: " + $item

    //Class using dispatcher (runtime resolution)
    RuntimeFormatter
      describe() as dispatcher
        -> item as Any
        <- rtn as String: "Any type"

      describe()
        -> item as Integer
        <- rtn as String: "Int: " + $item

      describe()
        -> item as String
        <- rtn as String: "Str: " + item

  defines program

    OverloadingVsDispatchDemo()
      stdout <- Stdout()

      formatter <- Formatter()

      // === COMPILE-TIME OVERLOADING ===
      // Compiler selects overload based on declared type
      stdout.println(formatter.formatItem("hello"))
      stdout.println(formatter.formatItem(42))
      stdout.println(formatter.formatItem(3.14))

      // === RUNTIME DISPATCHING ===
      // Dispatcher selects overload based on runtime type
      runtimeFmt <- RuntimeFormatter()

      //Direct calls: same as overloading
      stdout.println(runtimeFmt.describe(42))
      stdout.println(runtimeFmt.describe("hello"))

      //Heterogeneous list: runtime dispatch shines here
      items <- [1, 2.5, "text"]
      for item in items
        stdout.println(runtimeFmt.describe(item))

Common mistakes

E50001 — A pure method cannot call non-pure methods like stdout.println(). Pure methods must have no side effects. See ek9 -h E50001 for details.

Incorrect:

formatItem() as pure
        -> item as String
        <- rtn as String: "String: " + item
        stdout.println(item)

Correct:

formatItem() as pure
        -> item as String
        <- rtn as String: "String: " + item
Other ways to ask this
  • What is the difference between method overloading and dispatcher in EK9?
  • When should I use dispatcher instead of overloaded methods?
  • How does EK9 resolve overloaded method calls?

Coming from another language?

Java: method overloading resolved at compile time, visitor pattern for runtime dispatch, instanceof checks as workaround, no multiple dispatch. Python: no method overloading (last definition wins), functools.singledispatch for single parameter. JavaScript: no overloading, manual type checking with typeof. Rust: no method overloading, trait dispatch for polymorphism, match on enums. Go: no overloading, type switch for runtime dispatch. Kotlin: method overloading like Java, when with is for runtime checks, no dispatcher. C#: method overloading at compile time, dynamic keyword for runtime. Julia: multiple dispatch built into language, most similar to EK9 dispatcher. EK9: method overloading with cost-based compile-time resolution, 'dispatcher' keyword for runtime type-based dispatch, eliminates visitor pattern.

Keywords: type, compile-time, overloading, runtime, dispatcher, visitor, matching, dispatch, resolution, parameter, method, sealed, handler, cost