What is function dispatching in EK9?
← Getting Started · Ref: Q60
EK9 has a 'dispatcher' keyword that enables runtime type-based dispatch on method parameters. This is like method overloading but resolved at RUNTIME based on the actual types of the arguments, not just the declared types.
BASIC DISPATCHER
Mark a method as 'dispatcher' and provide overloaded implementations:
Formatter
format() as dispatcher
-> item as Any
<- result as String: $item
format()
-> item as Integer
<- result as String: "Int: " + $item
format()
-> item as String
<- result as String: "Str: " + item
When you call 'formatter.format(someValue)', EK9 dispatches to the most specific overload based on the runtime type of 'someValue'. If no specific overload matches, the dispatcher base method (with 'Any' parameter) handles it.
WHY IS THIS POWERFUL?
In Java, method overloading is resolved at COMPILE time based on declared types. If you have a variable typed as Object that holds an Integer, Java calls the Object overload. EK9 calls the Integer overload because it dispatches on the RUNTIME type.
This eliminates the visitor pattern. Instead of defining accept/visit methods across a class hierarchy, you write a single dispatcher method with overloads for each type.
DISPATCHER WITH MULTIPLE PARAMETERS
Dispatchers can dispatch on multiple parameters:
intersect() as dispatcher -> s1 as Shape, s2 as Shape <- result as String: "generic"
intersect()
-> s1 as Circle, s2 as Circle
<- result as String: "circle-circle"
intersect()
-> s1 as Circle, s2 as Rectangle
<- result as String: "circle-rectangle"
This is true multiple dispatch — something only Julia and Common Lisp natively support among mainstream languages.
DISPATCHER WITH TRAITS
Dispatchers work with trait types, using 'allow only' to restrict which types the dispatcher accepts. This enables safe dispatching over trait hierarchies.
DISPATCHER IS PURE-COMPATIBLE
Dispatchers can be marked 'as pure dispatcher' for side-effect-free dispatching.
In Java, you need the visitor pattern (verbose, fragile). In Python, you use functools.singledispatch (limited). In Go, you use type switches. In Rust, you use match with enum variants. EK9's dispatcher is built into the language with compile-time verification of completeness.
See Q49 for defining functions. See Q51 for abstract functions. See Q56 for higher-order functions as an alternative dispatch mechanism. See Q105 for method dispatch in classes. See Q255 for cost-based method resolution in overloading. See Q600 for method overloading vs function dispatching comparison.
Example
defines module qa.dispatching defines class Shape as abstract name() as abstract <- rtn as String? default operator ? Circle is Shape override name() <- rtn as String: "Circle" Square is Shape override name() <- rtn as String: "Square" Triangle is Shape override name() <- rtn as String: "Triangle" Renderer render() as dispatcher -> shape as Shape <- result as String: "Shape: " + shape.name() render() -> shape as Circle <- result as String: "Rendered Circle" render() -> shape as Square <- result as String: "Rendered Square" defines program DispatchDemo() stdout <- Stdout() renderer <- Renderer() // Dispatcher selects overload based on runtime type circle <- Circle() square <- Square() triangle <- Triangle() stdout.println(renderer.render(circle)) stdout.println(renderer.render(square)) // Triangle has no specific handler — falls through to base stdout.println(renderer.render(triangle))
Common mistakes
E05120 — When implementing an abstract method from a parent class, the 'override' keyword is required. Without it, the compiler treats it as a new method rather than an implementation. See ek9 -h E05120 for details.
Incorrect:
Circle is Shape name() <- rtn as String: "Circle"
Correct:
Circle is Shape override name() <- rtn as String: "Circle"
Other ways to ask this
- How does the dispatcher keyword work in EK9?
- How do I dispatch on multiple argument types in EK9?
- How does EK9 handle multiple dispatch?
Coming from another language?
Java: method overloading resolved at compile time (static dispatch), visitor pattern needed for runtime dispatch, verbose accept/visit boilerplate, instanceof checks are code smell. Python: functools.singledispatch for single-argument dispatch, limited to one parameter, no compile-time verification, runtime errors for missing handlers. JavaScript: no dispatch mechanism, manual typeof/instanceof checks, no type safety. Rust: match on enum variants, no multiple dispatch, trait methods for single dispatch, pattern matching is powerful but not multi-argument dispatch. Go: type switch for runtime dispatch, no multiple dispatch, no compile-time completeness check. C#: dynamic keyword for late binding, no multiple dispatch, visitor pattern needed. Kotlin: when with is checks for type matching, no multiple dispatch, smart casts help but are single-argument. Julia: built-in multiple dispatch as core language feature, most similar to EK9's dispatcher, but dynamically typed. Swift: no multiple dispatch, protocol-based single dispatch, switch with pattern matching. EK9: built-in 'dispatcher' keyword for runtime type-based dispatch, multiple dispatch on multiple parameters, compile-time verified, pure-compatible, eliminates visitor pattern, works with traits and class hierarchies.
Keywords: start, dispatch, sealed, visitor, first, pattern, migrate, trait, overload, runtime, dispatcher, side-effect, type, handler, intro, resolve, function, immutable, parameter, multiple, pure, beginner