Why can't I use 'default operator <=>' on a record with a function delegate field?

← Classes and OOP · Ref: Q834

EK9 prevents 'default operator <=>' (and similar auto-generated comparison operators) on records that contain function delegate fields. The reason: how do you meaningfully compare two function references? Reference equality is almost never useful.

THE PROBLEM

When you write 'default operator <=>' the compiler auto-generates comparison by comparing each field. For String or Integer fields this is clear. But for a function delegate field, what does 'delegate1 <=> delegate2' mean? There is no meaningful default comparison.

WHAT STILL WORKS

- 'default operator ?' (isSet) — always valid, checks if the delegate is assigned
- Explicit operator implementation — you can write your own comparison that ignores the delegate
- Records without delegate fields — all default operators work normally

THIS EXAMPLE

Class EventHandler wraps a function delegate with an explicit comparator. SimpleRecord (no delegates) uses all default operators freely.

See Q116 for default operators. See Q96 for operator overview. See Q98 for record operators.

Example

defines module qa.classesandoop.delegate.defaultoperator

  defines function

    <?-
      Abstract function type used as a delegate.
    -?>
    Handler as abstract
      -> event as String
      <- rtn as Boolean?

    LogHandler() is Handler
      -> event as String
      <- rtn as Boolean: event?

  defines class

    <?-
      Class wrapping a function delegate field.
      Uses explicit comparator since default comparator
      would not know how to compare function references.
    -?>
    EventHandler
      eventName <- String()
      handler as Handler?

      default private EventHandler()

      EventHandler()
        ->
          n as String
          h as Handler
        eventName :=: n
        handler := h

      fire()
        <- rtn as Boolean: handler(eventName)

      //Explicit comparator — compares by name, ignoring the delegate
      operator <=> as pure
        -> arg0 as EventHandler
        <- rtn as Integer: eventName <=> arg0.eventName

      override operator ? as pure
        <- rtn as Boolean: eventName? and handler?

  defines record

    <?-
      Record without delegates — all default operators work fine.
    -?>
    SimpleRecord
      label <- "none"

      SimpleRecord()
        -> l as String
        label :=: l

      operator <=> as pure
        -> arg0 as SimpleRecord
        <- rtn as Integer: label <=> arg0.label

      default operator

  defines program

    DelegateDemo()
      stdout <- Stdout()

      eh1 <- EventHandler("click", LogHandler)
      eh2 <- EventHandler("submit", LogHandler)

      if eh1?
        comparison <- eh1 <=> eh2
        stdout.println(`Comparison: ${comparison}`)
        stdout.println(`Fire: ${eh1.fire()}`)

      simple1 <- SimpleRecord("alpha")
      simple2 <- SimpleRecord("beta")
      if simple1?
        simpleResult <- simple1 <=> simple2
        stdout.println(`Simple: ${simpleResult}`)

Common mistakes

E07210 — Record has a function delegate field — 'default operator <=>' cannot be auto-generated because comparing function references has no meaningful semantics. Use 'default operator ?' (always valid) or implement comparison explicitly. See ek9 -h E07210 for details.

Incorrect:

      default operator <=>

Correct:

      operator <=> as pure
        -> arg0 as EventHandler
        <- rtn as Integer: eventName <=> arg0.eventName

E07210 — Cannot auto-generate all default operators for records with delegates. 'default operator' would include comparators which cannot compare function references. Implement operators explicitly. See ek9 -h E07210 for details.

Incorrect:

      default operator

Correct:

      override operator ? as pure
        <- rtn as Boolean: eventName? and handler?
Other ways to ask this
  • What is E07210 FUNCTION_DELEGATE_WITH_DEFAULT_OPERATORS?
  • Why does EK9 reject default comparator on my record with a delegate?
  • How do I compare records that contain function delegate fields?

Coming from another language?

Java: records with functional interface fields auto-generate equals/hashCode comparing by identity — usually meaningless. Kotlin: data classes with lambda fields use reference equality for lambdas. C#: records include delegates in equality using invocation list — fragile and surprising. EK9: rejects default comparison for delegate-containing records at compile time.

Keywords: comparison, field, default, comparator, delegate, isSet, operator, record, function, E07210