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 should implement ONLY the bridge trait, not the individual traits directly. If Duck lists the bridge AND the individual traits, whether the bridge handler still wins depends on the order they are listed in: some orders leave all three handlers tied at the same cost and the dispatch is ambiguous. Implementing only the bridge trait is the order-independent form.

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 the bridge handler cost 0.10, the individual trait handlers cost 0.20
    Duck with trait of AmphibiousFlyer
      override canFly()
        <- rtn as Boolean: true
      override canSwim()
        <- rtn as Boolean: true

    //The dispatcher has a handler for each trait and for the bridge
    Describer
      describe() as dispatcher
        -> thing as Any
        <- rtn as String: "thing"

      describe()
        -> thing as Flyable
        <- rtn as String: "flyable"

      describe()
        -> thing as Swimmable
        <- rtn as String: "swimmable"

      describe()
        -> thing as AmphibiousFlyer
        <- rtn as String: "bridge"

    //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()

      describer <- Describer()
      require describer.describe(duck) == "bridge"
      require describer.describe(eagle) == "flyable"
      require describer.describe(fish) == "swimmable"

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:

with trait of AmphibiousFlyer
      canFly()

Correct:

with trait of AmphibiousFlyer
      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: visitor, bridge, dispatch, diamond, multiple, implement, validate, cost, trait, ambiguity, sealed, E05230, handler