How does dispatcher resolution work across a class hierarchy?
← Dispatcher Validation · Ref: Q620
EK9 uses a cost-based model to resolve dispatchers. When a dispatcher is called, the compiler has already validated the hierarchy at compile time. At runtime, the most specific (lowest cost) handler is selected.
COST MODEL
Exact type match: cost 0.00. Each class inheritance step: cost 0.05 per level. Each trait implementation step: cost 0.10 per level. The handler with the lowest total cost wins.
FALLBACK TO ENTRY
If no specific handler matches, the dispatcher entry method executes. This is the catch-all fallback for types without dedicated handlers.
INHERITANCE DEPTH MATTERS
A handler for a direct parent (cost 0.05) beats a handler for a grandparent (cost 0.10). More specific handlers always win over less specific ones.
COMPILE-TIME VALIDATION
All type hierarchy checks (E05210), return type checks (E05220), purity checks (E05170), and access checks (E05180) happen at compile time. Runtime dispatch only selects the handler; correctness is guaranteed by the compiler.
TRAIT VS CLASS COST
Trait implementation costs more than class inheritance (0.10 vs 0.05). This reflects the structural reality that class inheritance is a stronger type relationship than trait implementation.
See Q60 for dispatcher fundamentals. See Q612 for purity matching. See Q614 for type hierarchy validation. See Q255 for cost-based method resolution.
Example
defines module qa.dispatchervalidation.rules defines class //Three-level hierarchy to demonstrate cost-based resolution Entity as abstract kind() as pure abstract <- rtn as String? default operator ? LivingEntity extends Entity as open override kind() as pure <- rtn as String: "living" default operator ? Person extends LivingEntity override kind() as pure <- rtn as String: "person" default operator ? //Dispatcher with handlers at different hierarchy depths EntityProcessor process() as dispatcher -> entity as Entity <- rtn as String: "Generic: " + entity.kind() //Cost 0.05 from LivingEntity (one step from Entity) process() -> entity as LivingEntity <- rtn as String: "Living: " + entity.kind() //Cost 0.10 from Person (two steps from Entity) //But cost 0.00 for exact Person match process() -> entity as Person <- rtn as String: "Person: " + entity.kind() defines function testCostBasedResolution() processor <- EntityProcessor() person <- Person() //Person matches Person handler at cost 0.00 (exact match) require processor.process(person) == "Person: person"
Common mistakes
E50060 — The dispatcher entry takes Entity. All handler parameter types must be within the Entity hierarchy. String is unrelated and would never match. See ek9 -h E50060 for details.
Incorrect:
process()
-> entity as String
Correct:
process()
-> entity as Person
E50060 — String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details.
Incorrect:
require processor.process(person).toUpperCase() == "Person: person"
Correct:
require processor.process(person) == "Person: person"
Other ways to ask this
- What are the dispatch resolution rules in EK9?
- How does EK9 choose which handler to call?
- What is the cost model for dispatcher resolution?
Coming from another language?
Java: virtual dispatch via vtable (single dispatch, O(1)). C++: virtual function tables with dynamic_cast for type checking. Python: MRO-based method resolution. Kotlin: no runtime dispatch, compile-time overload resolution. Julia: multiple dispatch with method cache. EK9: cost-based multiple dispatch with compile-time hierarchy validation.
Keywords: ambiguity, class, visitor, compile, entry, hierarchy, fallback, dispatch, resolution, depth, validate, sealed, handler, cost