Delegate operators through a trait so the delegating class supports $ and ?.

← Sealed Types and Traits · Ref: Q1133

'by' delegation forwards EVERY member of the trait that the class does not define itself - methods AND operators, '$' and '?' included:

  PrinterWrapper with trait of Printable by delegate
    delegate as Printable?

Declare the operators on the trait, and the wrapper answers them through the delegate: '$wrapper' is the delegate's '$', 'wrapper?' is the delegate's '?', and a binary operator such as '+' is the delegate's '+'. The wrapper needs no 'operator ?' of its own, because the trait declares one.

A member the class declares itself is not forwarded: write 'override operator $' on the wrapper and its own '$' answers, while everything else is still forwarded.

Only members the TRAIT provides are forwarded: those it declares AND those it inherits from the traits it extends, but never what only Any supplies. If the trait provides no '$', the wrapper's '$' is its own (for example one 'default operator' generates from its fields).

The delegate is just another provider, so 'by' never resolves a conflict: if the super class, another delegated trait or a trait with a body also provides a member, the class must declare it (E06150 otherwise), and its body may call super or the delegate.

A trait operator that also exists on Any, such as '?', needs 'override' on the trait (E05120 otherwise).

See Q1129 for basic delegation. See Q1130 for chained delegation.

Example

#!ek9
defines module qa.sealedtraits.traitoperatordelegation

  defines trait

    Printable
      describe() as pure
        <- rtn as String: "default"

      operator $ as pure
        <- rtn as String: "trait-dollar"

      override operator ? as pure
        <- rtn as Boolean: true

      operator + as pure
        -> amount as Integer
        <- rtn as String: "trait-plus"

  defines class

    RealPrinter with trait of Printable
      model as String?

      default private RealPrinter()

      RealPrinter()
        -> initial as String
        model :=? initial

      override describe() as pure
        <- rtn as String: "RealPrinter"

      override operator $ as pure
        <- rtn as String: "real-dollar"

      //Set only when it has a model
      override operator ? as pure
        <- rtn as Boolean: model?

      override operator + as pure
        -> amount as Integer
        <- rtn as String: `real-plus-${amount}`

    //Forwards describe(), '$', '?' and '+' to the delegate - no operators of its own
    PrinterWrapper with trait of Printable by delegate
      delegate as Printable?

      default private PrinterWrapper()

      PrinterWrapper()
        -> printer as Printable
        delegate := printer

    //Declares its own '$', which wins; everything else is still forwarded
    LabelledWrapper with trait of Printable by delegate
      delegate as Printable?

      default private LabelledWrapper()

      LabelledWrapper()
        -> printer as Printable
        delegate := printer

      override operator $ as pure
        <- rtn as String: "labelled-dollar"

  defines program

    TraitOperatorDelegationDemo()
      stdout <- Stdout()

      wrapper <- PrinterWrapper(RealPrinter("laser"))

      //Delegated method call
      stdout.println(`describe: ${wrapper.describe()}`)

      //Delegated operators: each is answered by RealPrinter
      stdout.println(`dollar: ${wrapper}`)
      stdout.println(`isSet: ${wrapper?}`)
      stdout.println(`no model isSet: ${PrinterWrapper(RealPrinter(String()))?}`)
      stdout.println(`plus: ${wrapper + 3}`)

      //The wrapper's own '$' wins over the delegate's
      labelled <- LabelledWrapper(RealPrinter("inkjet"))
      stdout.println(`labelled dollar: ${labelled}`)
      stdout.println(`labelled describe: ${labelled.describe()}`)

Common mistakes

E05120 — '?' already exists on Any, which every trait extends, so declaring it on the trait overrides it and needs 'override'. '$' is not on Any, so it takes no 'override' on the trait.

Incorrect:

      operator ? as pure
        <- rtn as Boolean: true

Correct:

      override operator ? as pure
        <- rtn as Boolean: true
Other ways to ask this
  • I need a wrapper class that delegates not just methods but also operators via a trait
  • In Kotlin 'by' delegates interface methods. Show EK9 trait operator delegation
  • Given a trait with operators, create a delegating class that forwards them
  • Make a delegating wrapper support $ string and ? isSet through its delegate

Coming from another language?

Kotlin: class Wrapper(p: Printable): Printable by p - delegates the interface's members, and a member the class writes itself wins. EK9: 'with trait of X by field' - delegates the trait's methods and operators the same way, and a member the class declares itself wins.

Keywords: $, ?, by, delegation, forward, operator, trait