Find the cheaper Product from two optional product records.

← Operators and Expressions · Ref: Q1228

Define 'default operator' on the record to auto-generate <=> from fields (compared in declaration order). Then use <? in a pure function to pick the lesser product.

  cheaperProduct() as pure
    -> left as Product, right as Product
    <- rtn as Product: left <? right

The <? coalescing minimum operator works on ANY type that has <=> defined — not just built-in types. If both values are set, <? returns the lesser. If one is unset, it returns the set one. If both are unset, the result is unset.

See Q1049 for comparison operators on records. See Q1208 for <? on built-in Date type.

Example

defines module qa.operators.coalescemin.record

  defines record

    Product
      name as String: String()
      price as Float: 0.0

      Product()
        ->
          name as String
          price as Float
        this.name: name
        this.price: price

      default operator

  defines function

    cheaperProduct() as pure
      ->
        left as Product
        right as Product
      <- rtn as Product: left <? right

  defines program

    CoalesceMinRecordDemo()
      stdout <- Stdout()

      // === BOTH SET — returns the lesser by natural ordering ===

      widget <- Product("Widget", 19.99)
      gadget <- Product("Gadget", 29.99)
      cheaper <- cheaperProduct(widget, gadget)
      stdout.println(`Cheaper product: ${cheaper}`)

      // === ONE UNSET — returns the set one ===

      knownProduct <- Product("Keyboard", 49.99)
      unknownProduct <- Product()
      available <- cheaperProduct(knownProduct, unknownProduct)
      stdout.println(`Available product: ${available}`)

      // === BOTH UNSET — result is unset ===

      missingA <- Product()
      missingB <- Product()
      noProduct <- cheaperProduct(missingA, missingB)
      if noProduct?
        stdout.println(`Product: ${noProduct}`)
      else
        stdout.println("No product available")

      // === INLINE USAGE ===

      monitor <- Product("Monitor", 299.99)
      mouse <- Product("Mouse", 12.99)
      bestDeal <- monitor <? mouse
      stdout.println(`Best deal: ${bestDeal}`)

Common mistakes

E07235 — The <? operator requires <=> to be defined. Use 'default operator' on the record to auto-generate it. See ek9 -h E15200 for details.

Incorrect:

      //no operator defined

Correct:

      default operator
Other ways to ask this
  • How do I use <? coalescing on a custom record type?
  • Given two optional Product records, pick the one with the lower natural ordering.
  • In Java I'd use Comparator.comparing(Product::getPrice). How does EK9 handle this?
  • Migrating from Kotlin where I use minOf(a, b) for data classes — what does EK9 offer?

Coming from another language?

Java: Stream.of(a, b).filter(Objects::nonNull).min(Comparator.naturalOrder()). Python: min(a, b, key=lambda p: (p.name, p.price)). Kotlin: minOf(a, b). EK9: left <? right — one operator, handles unset automatically.

Keywords: <?, coalescing, default operator, user-defined, cheaper, minimum, product, record