How does adding a concrete type handler resolve dispatcher ambiguity?

← Dispatcher Validation · Ref: Q870

When a class implements two traits and a dispatcher has handlers for both traits, dispatching that class is ambiguous (E05230) because both handlers match at equal cost (0.10 each). Adding a handler for the concrete type resolves the ambiguity because exact match costs 0.00.

THE PROBLEM

Duck implements Flyable and Swimmable. Handlers exist for both traits at cost 0.10 each. Dispatching Duck is ambiguous.

THE SOLUTION

Add a handler specifically for Duck (cost 0.00 — exact match). This always wins over trait handlers.

CORRECT PATTERN

  process()
    -> animal as Duck
    <- rtn as String: animal.speak()

Exact match handler at cost 0.00 beats trait handlers at cost 0.10.

INCORRECT PATTERN (removes Duck handler)
Without the Duck handler, Duck matches both Flyable (0.10) and Swimmable (0.10) at equal cost. Ambiguity.

See Q616 for ambiguity basics. See Q617 for diamond trait dispatch.

Example

defines module qa.dispatchervalidation.ambiguity.resolution

  defines trait

    Flyable
      fly() as abstract
        <- rtn as String?

    Swimmable
      swim() as abstract
        <- rtn as String?

  defines class

    Animal as abstract
      speak() as abstract
        <- rtn as String?
      default operator ?

    Dog extends Animal
      override speak()
        <- rtn as String: "woof"

    Duck extends Animal with trait of Flyable, Swimmable
      override speak()
        <- rtn as String: "quack"
      override fly()
        <- rtn as String: "flapping"
      override swim()
        <- rtn as String: "paddling"
      override operator ? as pure
        <- rtn as Boolean: true

    <?-
      Dispatcher with handlers for Animal (base), Flyable, Swimmable,
      and Duck (concrete). The Duck handler resolves ambiguity.
    -?>
    AnimalProcessor
      process() as dispatcher
        -> animal as Animal
        <- rtn as String: animal.speak()

      process()
        -> animal as Dog
        <- rtn as String: animal.speak()

      process()
        -> creature as Flyable
        <- rtn as String: creature.fly()

      process()
        -> creature as Swimmable
        <- rtn as String: creature.swim()

      //Duck handler resolves ambiguity (cost 0.00 beats trait cost 0.10)
      process()
        -> animal as Duck
        <- rtn as String: animal.speak()

  defines function

    testAmbiguityResolution()
      processor <- AnimalProcessor()
      dog <- Dog()
      duck <- Duck()
      require processor.process(dog) == "woof"
      require processor.process(duck) == "quack"

Common mistakes

E05230 — Without the Duck-specific handler, Duck matches both Flyable and Swimmable at equal cost (0.10 each). The dispatcher cannot choose. Add a concrete type handler to resolve. See ek9 -h E05230 for details.

Incorrect:

      //Duck handler removed — ambiguity not resolved

Correct:

      //Duck handler resolves ambiguity (cost 0.00 beats trait cost 0.10)
      process()
        -> animal as Duck
        <- rtn as String: animal.speak()
Other ways to ask this
  • What triggers E05230 DISPATCHER_HANDLER_AMBIGUITY?
  • How do I fix dispatcher ambiguity for types implementing multiple traits?
  • Why does replacing a concrete handler with a trait handler cause ambiguity?

Coming from another language?

Java: method overloading resolved at compile time, no runtime dispatch ambiguity. C++: multiple inheritance ambiguity resolved with virtual base classes. Python: C3 linearization provides deterministic ordering. Kotlin: compiler error for interface default method conflicts. EK9: E05230 detects dispatch ambiguity at compile time, requires structural resolution.

Keywords: E05230, cost, trait, resolution, handler, dispatcher, ambiguity, concrete, duck