What is dispatcher ambiguity and how is it detected?
← Dispatcher Validation · Ref: Q616
Dispatcher ambiguity (E05230) occurs when two or more handlers match an argument type at equal cost. EK9 uses a cost-based dispatch model and refuses to guess when costs are tied.
COST MODEL
EK9 assigns dispatch costs based on type distance: exact type match costs 0.00, each class inheritance step costs 0.05, and each trait implementation step costs 0.10. The handler with the lowest cost wins.
WHEN AMBIGUITY OCCURS
Ambiguity arises when a class implements two traits and both traits have handlers at the same cost. Since both trait implementations are at equal distance, the dispatcher cannot choose.
EXAMPLE
If Duck implements both Flyable and Swimmable, and the dispatcher has handlers for both traits at the same depth, dispatching a Duck is ambiguous (both handlers cost 0.10).
RESOLUTION STRATEGIES
Strategy 1 - Explicit handler: add a handler for the ambiguous concrete type (exact match at cost 0.00 wins). Strategy 2 - Bridge trait: create a combining trait that encompasses both competing traits, giving it a shorter path. Strategy 3 - Decomposed dispatchers: split into separate dispatchers, each handling one concern.
EK9 REFUSES TO GUESS
Unlike languages that pick 'first declared' or 'most recently added', EK9 treats ambiguity as a design error. The compiler requires structural resolution.
See Q617 for diamond trait ambiguity. See Q60 for dispatcher fundamentals. See Q255 for cost-based method resolution.
Example
defines module qa.dispatchervalidation.ambiguity defines class //CORRECT: No ambiguity because only one trait hierarchy is dispatched Vehicle as abstract kind() as pure abstract <- rtn as String? default operator ? Car extends Vehicle override kind() as pure <- rtn as String: "car" default operator ? Truck extends Vehicle override kind() as pure <- rtn as String: "truck" default operator ? Motorcycle extends Vehicle override kind() as pure <- rtn as String: "motorcycle" default operator ? //Clear hierarchy: Car, Truck, Motorcycle all extend Vehicle //No ambiguity because single-parent inheritance has clear costs VehicleProcessor process() as dispatcher -> vehicle as Vehicle <- rtn as String: "Vehicle: " + vehicle.kind() process() -> vehicle as Car <- rtn as String: "Car: " + vehicle.kind() process() -> vehicle as Truck <- rtn as String: "Truck: " + vehicle.kind() process() -> vehicle as Motorcycle <- rtn as String: "Motorcycle: " + vehicle.kind() defines function testUnambiguousDispatch() processor <- VehicleProcessor() car <- Car() truck <- Truck() moto <- Motorcycle() require processor.process(car) == "Car: car" require processor.process(truck) == "Truck: truck" require processor.process(moto) == "Motorcycle: motorcycle"
Common mistakes
E50060 — String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details.
Incorrect:
require processor.process(car).toUpperCase() == "Car: car"
Correct:
require processor.process(car) == "Car: car"
E50010 — The dispatcher entry takes Vehicle. A handler for Shape is outside the Vehicle hierarchy and can never be reached through dispatch. See ek9 -h E50010 for details.
Incorrect:
process()
-> shape as Shape
Correct:
process()
-> vehicle as Truck
Other ways to ask this
- What is E05230 in EK9?
- When does a dispatcher become ambiguous?
- How does EK9 resolve dispatcher cost ties?
Coming from another language?
Java: method overloading resolved at compile time, no runtime ambiguity concept. C++: multiple inheritance ambiguity resolved with virtual base classes. Python: MRO (C3 linearization) provides deterministic resolution. Kotlin: compiler error for ambiguous overloads. Julia: multiple dispatch with ambiguity detection. EK9: E05230 detects runtime dispatch ambiguity at compile time, requires structural resolution.
Keywords: dispatcher, explicit, trait, ambiguity, bridge, cost, E05230, validate, tie, resolution, dispatch