Should I write #? and $ manually or use default operator?

← Operators and Expressions · Ref: Q1055

Use 'default operator' when field-by-field behaviour is correct. Write manual operators only when you need custom logic.

DEFAULT OPERATOR (recommended for most types):

  defines record
    Address
      street as String: String()
      city as String: String()
      default operator

This generates #?, $, ==, <>, <=>, and ? automatically from all fields. The generated $ concatenates field strings. The generated #? combines field hashes.

MANUAL OPERATORS (when you need custom behaviour):

  defines class
    Account
      accountId as Integer: 0
      balance as Float: 0.0
      operator #? as pure
        <- rtn as Integer: #? accountId
      operator $ as pure
        <- rtn as String: `Account-${accountId}`

Here #? hashes only the accountId (not balance), and $ shows a formatted string. Default operator would hash both fields and show both in the string.

YOU CAN MIX: use 'default operator' then override specific ones:

  default operator
  override operator $ as pure
    <- rtn as String: `Custom: ${name}`

The default generates all operators, then your override replaces $.

See Q116 for what default operator generates. See Q1047 for #? on records. See Q1053 for manual #? on classes. See Q949 for default operator rules.

Example

defines module qa.operators.defaultvsmanual

  defines record

    //DEFAULT: all operators generated from fields
    Address
      street as String: String()
      city as String: String()

      Address()
        ->
          street as String
          city as String
        this.street :=: street
        this.city :=: city

      default operator

  defines class

    //MANUAL: custom hashcode and string
    Account
      accountId as Integer: 0
      balance as Float: 0.0

      default private Account()

      Account()
        ->
          accountId as Integer
          balance as Float
        this.accountId :=: accountId
        this.balance :=: balance

      //Hash only by accountId, not balance
      operator #? as pure
        <- rtn as Integer: #? accountId

      //Custom display format
      operator $ as pure
        <- rtn as String: `Account-${accountId}: ${balance}`

      override operator ? as pure
        <- rtn as Boolean: accountId? and balance?

  defines program

    DefaultVsManualDemo()
      stdout <- Stdout()

      //Record with default operator
      addr <- Address("123 Main St", "London")
      stdout.println(`Address: ${addr}`)
      stdout.println(`Hash: ${#? addr}`)

      //Class with manual operators
      acct <- Account(1001, 5250.75)
      stdout.println(`${acct}`)
      stdout.println(`Hash: ${#? acct}`)

Common mistakes

E08180 — For simple value types, 'default operator' generates all standard operators in one line. Manual implementation is only needed for custom behaviour.

Incorrect:

      operator == as pure
        ...
      operator $ as pure
        ...
      operator #? as pure
        ...

Correct:

      default operator
Other ways to ask this
  • When should I use default operator instead of writing operators by hand?
  • Show the difference between default operator and manual #? and $ implementations
  • Can I mix default operator with custom operator overrides?

Coming from another language?

Java: records auto-generate, classes need manual. Python: @dataclass auto-generates. EK9: 'default operator' works on both records and classes.

Keywords: string, default, operator, generate, manual, hashcode, override