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

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: operator, trait, delegation, ?, $, forward, by