How do I implement double dispatch in EK9?
← Design Patterns and Idioms · Ref: Q211
EK9 has built-in double dispatch via the 'as dispatcher' modifier. Mark a method as a dispatcher and provide overloads for specific types. The runtime selects the most specific match.
DISPATCHER METHOD
Mark the base method with 'as dispatcher':
render() as dispatcher -> shape as Shape
This method is the dispatch entry point.
SPECIFIC OVERLOADS
Provide overloaded methods for specific subtypes:
render()
-> circle as Circle
stdout.println("Rendering circle")
render()
-> rect as Rectangle
stdout.println("Rendering rectangle")
RUNTIME DISPATCH
Calling render(someShape) dispatches to the most specific overload based on the actual runtime type of someShape.
NO VISITOR BOILERPLATE
Unlike the Visitor pattern, no accept() methods needed on element classes. The dispatcher handles type resolution automatically.
See Q60 for function dispatching. See Q103 for abstract classes. See Q105 for method dispatch. See Q254 for the Any type as dispatcher fallback. See Q255 for method resolution costs in dispatching.
Example
defines module qa.patterns.doubledispatch defines class Shape as abstract shapeName as String? default private Shape() as pure Shape() as pure -> n as String shapeName :=? String(n) name() as pure <- rtn as String: String(shapeName) override operator ? as pure <- rtn as Boolean: shapeName? Circle is Shape Circle() as pure super("Circle") Rectangle is Shape Rectangle() as pure super("Rectangle") // Renderer with dispatcher-based double dispatch Renderer stdout as Stdout: Stdout() // Base dispatcher method render() as dispatcher -> shape as Shape stdout.println("Rendering generic shape: " + shape.name()) // Specific overload for Circle render() -> circle as Circle stdout.println("Rendering circle: " + circle.name()) // Specific overload for Rectangle render() -> rect as Rectangle stdout.println("Rendering rectangle: " + rect.name()) default operator ? defines program DoubleDispatchDemo() renderer <- Renderer() // === DIRECT CALLS === renderer.render(Circle()) renderer.render(Rectangle()) // === DISPATCH VIA BASE TYPE === // When called with Shape reference, // dispatcher resolves to most specific overload shapes <- List() of Shape shapes += Circle() shapes += Rectangle() for shape in shapes renderer.render(shape)
Common mistakes
E50060 — Shape does not have a label() method. The correct method name is name(). Calling a non-existent method triggers E50060 — method not resolved. See ek9 -h E50060 for details.
Incorrect:
stdout.println("Rendering generic shape: " + shape.label())
Correct:
stdout.println("Rendering generic shape: " + shape.name())
Other ways to ask this
- How do I use dispatcher for the visitor pattern in EK9?
- How do I replace the visitor pattern in EK9?
- How does multi-method dispatch work in EK9?
Coming from another language?
Java: Visitor pattern with accept/visit methods, or instanceof chains. Kotlin: when + is checks with sealed classes. C++: double dispatch via RTTI or visitor. Rust: enum match (no double dispatch). Go: type switch. EK9: 'as dispatcher' modifier with overloaded methods, runtime selects most specific match.
Keywords: pattern, idiom, design, multiple, dispatch, double, visitor, handler, overload, sealed, dispatcher, runtime, type