How can trait diamond patterns cause dispatcher ambiguity?

← Dispatcher Validation · Ref: Q617

When a class implements two traits and a dispatcher has handlers for both traits, the dispatch cost to both handlers is identical (0.10 each). This creates ambiguity (E05230) because the compiler cannot determine which handler should win.

THE DIAMOND PROBLEM

Consider: Duck implements both Flyable and Swimmable. A dispatcher has handlers for Flyable (cost 0.10) and Swimmable (cost 0.10). When dispatching a Duck, both handlers are equally good matches. This is the diamond problem applied to dispatch.

BRIDGE TRAIT SOLUTION

Create a combining trait that encompasses both competing traits:

  AmphibiousFlyer with trait of Flyable, Swimmable

Then Duck implements ONLY AmphibiousFlyer (not Flyable and Swimmable directly). The dispatcher handler for AmphibiousFlyer has cost 0.10, while Flyable and Swimmable handlers have cost 0.20 (two hops through the bridge). The bridge handler wins.

CRITICAL: the concrete type must implement ONLY the bridge trait, not the individual traits directly. If Duck implements all three, all handlers are at cost 0.10 and ambiguity remains.

EXPLICIT HANDLER SOLUTION

Add a handler for the specific ambiguous type (e.g., Duck) at cost 0.00 (exact match), which beats any trait handler.

See Q616 for ambiguity basics. See Q60 for dispatcher fundamentals. See Q107 for multiple trait implementation.

Example

defines module qa.dispatchervalidation.diamond

  defines trait

    //Bridge trait pattern to resolve diamond ambiguity
    Flyable
      canFly() as abstract
        <- rtn as Boolean?

    Swimmable
      canSwim() as abstract
        <- rtn as Boolean?

    //Bridge trait: combines both capabilities
    AmphibiousFlyer with trait of Flyable, Swimmable

  defines class

    //Duck implements ONLY the bridge trait (not Flyable/Swimmable directly)
    //This gives bridge handler cost 0.10, individual traits cost 0.20
    Duck with trait of AmphibiousFlyer
      override canFly()
        <- rtn as Boolean: true
      override canSwim()
        <- rtn as Boolean: true

    //Eagle implements only Flyable
    Eagle with trait of Flyable
      override canFly()
        <- rtn as Boolean: true

    //Fish implements only Swimmable
    Fish with trait of Swimmable
      override canSwim()
        <- rtn as Boolean: true

  defines function

    testBridgeTrait()
      duck <- Duck()
      eagle <- Eagle()
      fish <- Fish()

      require duck.canFly()
      require duck.canSwim()
      require eagle.canFly()
      require fish.canSwim()

Common mistakes

E50060 — Boolean has no booleanValue() method in EK9. Boolean values are used directly in conditions. See ek9 -h E50060 for details.

Incorrect:

require duck.canFly().booleanValue()

Correct:

require duck.canFly()

E05120 — When implementing a trait abstract method, the override keyword is required. Omitting it on canFly triggers shadowing detection. See ek9 -h E05120 for details.

Incorrect:

canFly()

Correct:

override canFly()
Other ways to ask this
  • What is the diamond problem in EK9 dispatchers?
  • How do multiple trait implementations affect dispatch?
  • Why does implementing two traits cause E05230?

Coming from another language?

Java: no runtime dispatch, diamond problem handled by interface default method rules. C++: virtual inheritance for diamond problem. Python: C3 linearization (MRO) provides deterministic resolution. Kotlin: explicit override resolution for diamond conflicts. Rust: no class inheritance, trait coherence rules. EK9: E05230 detects diamond dispatch ambiguity, resolved structurally via bridge traits or explicit handlers.

Keywords: trait, cost, validate, implement, multiple, diamond, dispatch, bridge, visitor, handler, E05230, sealed, ambiguity