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 <=> that ignores the delegate, with a hand-written #? hashing the same fields
- 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

#!ek9
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
        -> other as EventHandler
        <- rtn as Integer: eventName <=> other.eventName

      //A hand-written <=> needs a hand-written #? hashing what it compares
      operator #? as pure
        <- rtn as Integer: #?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
        -> other as SimpleRecord
        <- rtn as Integer: label <=> other.label

      operator #? as pure
        <- rtn as Integer: #?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
        -> other as EventHandler
        <- rtn as Integer: eventName <=> other.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: E07210, comparator, comparison, default, delegate, field, function, isSet, operator, record