Question: Build a list of Colour values and check if a specific colour is in the palette.

Create a palette of colours and test membership using the contains operator.

I need to verify whether a given colour exists in a predefined set.

In CSS I'd compare hex codes as strings. How does EK9 check colour membership in a list?

Given a list of Colour literals, check if a target colour is present.

Answer:
EK9 Colour uses hex literals (#RRGGBB). Store colours in a List and use the 'contains' operator for membership testing.

PALETTE
  palette <- [#FF0000, #00FF00, #0000FF]

MEMBERSHIP CHECK
  if palette contains targetColour
    stdout.println("Colour found in palette")

The 'contains' operator checks element membership in a List. It uses the == operator internally, so it works with any type that supports equality. Colour literals and the == operator make palette management simple and type-safe.

See Q1159 for list contains. See Q1190 for colour equality. See Q1101 for is in with lists.

defines module qa.operators.colourpalette

  defines program

    ColourPaletteDemo()
      stdout <- Stdout()

      // === BUILD PALETTE ===

      red <- #FF0000
      green <- #00FF00
      blue <- #0000FF
      palette <- [red, green, blue]

      // === MEMBERSHIP CHECK ===

      targetColour <- #FF0000
      if palette contains targetColour
        stdout.println(`${targetColour} is in the palette`)

      // === COLOUR NOT IN PALETTE ===

      yellow <- #FFFF00
      if not (palette contains yellow)
        stdout.println(`${yellow} is not in the palette`)

      // === CHECK MULTIPLE COLOURS ===

      candidates <- [#FF0000, #FFFF00, #0000FF, #FF00FF]
      for candidate in candidates
        if palette contains candidate
          stdout.println(`${candidate}: in palette`)
        else
          stdout.println(`${candidate}: not in palette`)

      // === ADD TO PALETTE AND RECHECK ===

      extendedPalette <- palette + yellow
      if extendedPalette contains yellow
        stdout.println(`Yellow added to extended palette`)

      // === STREAM OUTPUT ===

      stdout.println("Full palette:")
      cat palette > stdout

Java: list.contains(color) with java.awt.Color. Python: color in palette with tuples or custom class. JavaScript: array.includes(color) with string hex codes. CSS: string comparison. EK9: palette contains targetColour — operator syntax, typed Colour values, no string comparison needed.

check collection colour contains hex list literal membership palette rgb

colour list membership
contains operator
colour palette


Question: Find the earlier of two delivery dates, even if one is unknown.

How do I pick the minimum of two optional dates using <? coalescing?

A supplier sends two possible delivery dates but one might be missing — pick the earlier one.

In Java I'd need null checks and compareTo to find the earlier date. What does EK9 offer?

Migrating from Python where I use min(d1, d2) with None checks — what is the EK9 equivalent?

Answer:
The <? coalescing minimum operator returns the lesser of two values, handling unset gracefully. Wrap it in a pure function.

  earlierOf() as pure
    -> left as Date, right as Date
    <- rtn as Date: left <? right

If both dates are set, <? returns the earlier one. If one is unset, it returns the set one. If both are unset, the result is unset. This is a COMPARISON operator — it does NOT assign anything.

Do not confuse <? with :=? (guarded assignment). The :=? operator assigns a value only if the target variable is unset — it is not a comparison. See Q1226 for a side-by-side contrast.

See Q1092 for min/max coalescing overview.

defines module qa.operators.coalescemin.dates

  defines function

    earlierOf() as pure
      ->
        left as Date
        right as Date
      <- rtn as Date: left <? right

  defines program

    CoalesceMinDatesDemo()
      stdout <- Stdout()

      // === BOTH DATES SET — returns the earlier ===

      deliveryA <- 2024-11-15
      deliveryB <- 2024-11-08
      earliest <- earlierOf(deliveryA, deliveryB)
      stdout.println(`Earliest delivery: ${earliest}`)

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

      confirmedDate <- 2024-12-01
      pendingDate <- Date()
      available <- earlierOf(confirmedDate, pendingDate)
      stdout.println(`Available date: ${available}`)

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

      unknownA <- Date()
      unknownB <- Date()
      noDate <- earlierOf(unknownA, unknownB)
      if noDate?
        stdout.println(`Date: ${noDate}`)
      else
        stdout.println("No delivery date available")

      // === INLINE USAGE ===

      shippingDate <- 2024-10-20
      warehouseDate <- 2024-10-25
      pickupDate <- shippingDate <? warehouseDate
      stdout.println(`Pickup by: ${pickupDate}`)

Java: d1 == null ? d2 : d2 == null ? d1 : d1.isBefore(d2) ? d1 : d2. Python: min(d for d in [d1, d2] if d is not None). Rust: [d1, d2].iter().flatten().min(). EK9: left <? right — one operator handles all cases.

<? coalescing comparison date delivery earlier minimum unset

<? coalescing minimum
date comparison
unset handling

Explanation: EK9 has no Math.min(); use the '<?' coalescing minimum operator, which also handles unset values. See ek9 -h E50001 for details.


Question: Select the cheaper of two price quotes where one vendor hasn't responded.

How do I find the minimum of two Money values when one might be unset?

A procurement system has two vendor quotes but one is pending — pick the cheaper safely.

In Java I'd need null checks before comparing BigDecimal prices. What does EK9 use?

Migrating from Go where I check nil before comparing prices — what is the EK9 pattern?

Answer:
The <? coalescing minimum operator returns the lesser of two Money values, handling unset gracefully.

  cheaperOf() as pure
    -> left as Money, right as Money
    <- rtn as Money: left <? right

If both prices are set, <? returns the cheaper one. If one is unset (vendor hasn't responded), it returns the available quote. If both are unset, the result is unset.

This is a COMPARISON operator — it picks the smaller value. Do not confuse it with :=? (guarded assignment), which assigns only if the target is unset. See Q1226 for a side-by-side contrast.

See Q1092 for min/max coalescing overview. See Q1200 for Money arithmetic.

defines module qa.operators.coalescemin.money

  defines function

    cheaperOf() as pure
      ->
        left as Money
        right as Money
      <- rtn as Money: left <? right

  defines program

    CoalesceMinMoneyDemo()
      stdout <- Stdout()

      // === BOTH QUOTES RECEIVED — returns the cheaper ===

      vendorA <- 149.99#USD
      vendorB <- 129.50#USD
      bestPrice <- cheaperOf(vendorA, vendorB)
      stdout.println(`Best price: ${bestPrice}`)

      // === ONE VENDOR HASN'T RESPONDED — returns the available quote ===

      receivedQuote <- 199.00#USD
      pendingQuote <- Money()
      onlyOption <- cheaperOf(receivedQuote, pendingQuote)
      stdout.println(`Available quote: ${onlyOption}`)

      // === NEITHER VENDOR RESPONDED — result is unset ===

      missingA <- Money()
      missingB <- Money()
      noQuote <- cheaperOf(missingA, missingB)
      if noQuote?
        stdout.println(`Quote: ${noQuote}`)
      else
        stdout.println("No vendor quotes available")

Java: a == null ? b : b == null ? a : a.compareTo(b) <= 0 ? a : b. Python: min(p for p in [a, b] if p is not None). Rust: no built-in money type. EK9: left <? right — one operator handles all cases.

<? cheaper coalescing minimum money price quote vendor

<? coalescing minimum
money comparison
price selection

Explanation: Money has no min() method; use the <? coalescing-minimum operator, which handles unset values and returns the lesser. See ek9 -h E50060 for details.


Question: Pick the shorter of two timeout durations for a retry policy.

How do I select the minimum Duration when one timeout might be unconfigured?

A retry policy has primary and fallback timeouts — pick the shorter one safely.

In Java I'd need null checks before comparing Duration objects. What does EK9 use?

Migrating from Python where I use min() with None filtering for timedeltas — what is the EK9 way?

Answer:
The <? coalescing minimum operator returns the shorter of two Duration values, handling unset gracefully.

  shorterTimeout() as pure
    -> left as Duration, right as Duration
    <- rtn as Duration: left <? right

If both durations are set, <? returns the shorter one. If one is unset (unconfigured), it returns the configured timeout. If both are unset, the result is unset.

This is a COMPARISON operator — it picks the smaller value. Do not confuse it with :=? (guarded assignment), which assigns only if the target is unset. See Q1226 for a side-by-side contrast.

See Q1092 for min/max coalescing overview. See Q1188 for Duration operations.

defines module qa.operators.coalescemin.duration

  defines function

    shorterTimeout() as pure
      ->
        left as Duration
        right as Duration
      <- rtn as Duration: left <? right

  defines program

    CoalesceMinDurationDemo()
      stdout <- Stdout()

      // === BOTH CONFIGURED — returns the shorter ===

      primaryTimeout <- PT30S
      fallbackTimeout <- PT15S
      chosenTimeout <- shorterTimeout(primaryTimeout, fallbackTimeout)
      stdout.println(`Chosen timeout: ${chosenTimeout}`)

      // === ONE UNCONFIGURED — returns the configured one ===

      configuredTimeout <- PT45S
      missingTimeout <- Duration()
      effectiveTimeout <- shorterTimeout(configuredTimeout, missingTimeout)
      stdout.println(`Effective timeout: ${effectiveTimeout}`)

      // === NEITHER CONFIGURED — result is unset ===

      missingFirst <- Duration()
      missingSecond <- Duration()
      noTimeout <- shorterTimeout(missingFirst, missingSecond)
      if noTimeout?
        stdout.println(`Timeout: ${noTimeout}`)
      else
        stdout.println("No timeout configured")

Java: a == null ? b : b == null ? a : a.compareTo(b) <= 0 ? a : b. Python: min(d for d in [a, b] if d is not None). Go: if a == 0 { return b } else if b == 0 { return a } else { return min(a, b) }. EK9: left <? right — one operator handles all cases.

<? coalescing duration minimum policy retry shorter timeout

<? coalescing minimum
duration comparison
timeout selection


Question: Find the earlier meeting time from two optional schedule slots.

How do I select the minimum Time when one schedule slot might be unfilled?

Two calendar slots are offered but one might be empty — pick the earlier time.

In Java I'd check for null before comparing LocalTime objects. What does EK9 use?

Migrating from Kotlin where I use listOfNotNull then minOrNull — what is the EK9 way?

Answer:
The <? coalescing minimum operator returns the earlier of two Time values, handling unset gracefully.

  earlierTime() as pure
    -> left as Time, right as Time
    <- rtn as Time: left <? right

If both times are set, <? returns the earlier one. If one is unset (empty slot), it returns the available time. If both are unset, the result is unset.

This is a COMPARISON operator — it picks the smaller value. Do not confuse it with :=? (guarded assignment), which assigns only if the target is unset. See Q1226 for a side-by-side contrast.

See Q1092 for min/max coalescing overview. See Q1187 for Time comparison.

defines module qa.operators.coalescemin.time

  defines function

    earlierTime() as pure
      ->
        left as Time
        right as Time
      <- rtn as Time: left <? right

  defines program

    CoalesceMinTimeDemo()
      stdout <- Stdout()

      // === BOTH SLOTS FILLED — returns the earlier ===

      morningSlot <- 09:30
      afternoonSlot <- 14:00
      firstAvailable <- earlierTime(morningSlot, afternoonSlot)
      stdout.println(`First available: ${firstAvailable}`)

      // === ONE SLOT EMPTY — returns the filled one ===

      confirmedSlot <- 11:00
      emptySlot <- Time()
      onlyOption <- earlierTime(confirmedSlot, emptySlot)
      stdout.println(`Only option: ${onlyOption}`)

      // === BOTH SLOTS EMPTY — result is unset ===

      noSlotA <- Time()
      noSlotB <- Time()
      noTime <- earlierTime(noSlotA, noSlotB)
      if noTime?
        stdout.println(`Time: ${noTime}`)
      else
        stdout.println("No meeting time available")

Java: a == null ? b : b == null ? a : a.isBefore(b) ? a : b. Python: min(t for t in [a, b] if t is not None). Kotlin: listOfNotNull(a, b).minOrNull(). EK9: left <? right — one operator handles all cases.

<? coalescing earlier meeting minimum schedule slot time

<? coalescing minimum
time comparison
schedule selection

Explanation: Time has no isBefore() method; use the '<?' coalescing-minimum operator to pick the earlier value. See ek9 -h E50060 for details.


Question: Get the lower of two sensor readings where one sensor might be offline.

How do I find the minimum of two Integer values when one might be unset?

A monitoring system has two sensors but one could be offline — pick the lower reading.

In Java I'd need Optional and compareTo for nullable integers. What does EK9 use?

Migrating from C# where I use Math.Min with nullable checks — what is the EK9 pattern?

Answer:
The <? coalescing minimum operator returns the lesser of two Integer values, handling unset gracefully.

  lowerReading() as pure
    -> left as Integer, right as Integer
    <- rtn as Integer: left <? right

If both readings are set, <? returns the lower one. If one is unset (sensor offline), it returns the active sensor's reading. If both are unset, the result is unset.

This is a COMPARISON operator — it picks the smaller value. Do not confuse it with :=? (guarded assignment), which assigns only if the target is unset. See Q1226 for a side-by-side contrast.

See Q1092 for min/max coalescing overview. See Q1227 for all four coalescing operators.

defines module qa.operators.coalescemin.integer

  defines function

    lowerReading() as pure
      ->
        left as Integer
        right as Integer
      <- rtn as Integer: left <? right

  defines program

    CoalesceMinIntegerDemo()
      stdout <- Stdout()

      // === BOTH SENSORS ONLINE — returns the lower reading ===

      sensorA <- 42
      sensorB <- 37
      lowestReading <- lowerReading(sensorA, sensorB)
      stdout.println(`Lowest reading: ${lowestReading}`)

      // === ONE SENSOR OFFLINE — returns the active one ===

      activeSensor <- 55
      offlineSensor <- Integer()
      onlyReading <- lowerReading(activeSensor, offlineSensor)
      stdout.println(`Active reading: ${onlyReading}`)

      // === BOTH SENSORS OFFLINE — result is unset ===

      downA <- Integer()
      downB <- Integer()
      noReading <- lowerReading(downA, downB)
      if noReading?
        stdout.println(`Reading: ${noReading}`)
      else
        stdout.println("No sensor data available")

Java: Optional.ofNullable(a).flatMap(x -> Optional.ofNullable(b).map(y -> Math.min(x, y))).orElse(a != null ? a : b). Python: min(x for x in [a, b] if x is not None). EK9: left <? right — one operator handles all cases.

<? coalescing integer lower minimum offline reading sensor

<? coalescing minimum
integer comparison
sensor monitoring

<- rtn as Integer: Math.min(left, right)

Explanation: EK9 has no 'Math.min()'; use the '<?' coalescing-minimum operator, which also handles unset values. See ek9 -h E50001 for details.


Question: Select the lower temperature from two weather stations.

How do I find the minimum of two Float values when one station might be offline?

Two weather stations report temperature but one might be down — pick the lower reading.

In Java I'd need null checks before Math.min for nullable doubles. What does EK9 use?

Migrating from Python where I use min() with None filtering for floats — what is the EK9 pattern?

Answer:
The <? coalescing minimum operator returns the lesser of two Float values, handling unset gracefully.

  lowerTemp() as pure
    -> left as Float, right as Float
    <- rtn as Float: left <? right

If both temperatures are set, <? returns the lower one. If one is unset (station offline), it returns the active station's reading. If both are unset, the result is unset.

This is a COMPARISON operator — it picks the smaller value. Do not confuse it with :=? (guarded assignment), which assigns only if the target is unset. See Q1226 for a side-by-side contrast.

See Q1092 for min/max coalescing overview. See Q1227 for all four coalescing operators.

defines module qa.operators.coalescemin.float

  defines function

    lowerTemp() as pure
      ->
        left as Float
        right as Float
      <- rtn as Float: left <? right

  defines program

    CoalesceMinFloatDemo()
      stdout <- Stdout()

      // === BOTH STATIONS REPORTING — returns the lower temperature ===

      stationAlpha <- 18.5
      stationBravo <- 22.3
      coldest <- lowerTemp(stationAlpha, stationBravo)
      stdout.println(`Coldest reading: ${coldest}`)

      // === ONE STATION OFFLINE — returns the active one ===

      activeStation <- 15.7
      offlineStation <- Float()
      onlyReading <- lowerTemp(activeStation, offlineStation)
      stdout.println(`Active station: ${onlyReading}`)

      // === BOTH STATIONS OFFLINE — result is unset ===

      downAlpha <- Float()
      downBravo <- Float()
      noReading <- lowerTemp(downAlpha, downBravo)
      if noReading?
        stdout.println(`Temperature: ${noReading}`)
      else
        stdout.println("No weather data available")

Java: a == null ? b : b == null ? a : Math.min(a, b). Python: min(t for t in [a, b] if t is not None). Go: custom function with nil checks. EK9: left <? right — one operator handles all cases.

<? coalescing float lower minimum station temperature weather

<? coalescing minimum
float comparison
weather monitoring

Explanation: EK9 has no Math.min() — use the coalescing-minimum operator '<?', which returns the lesser value and handles unset operands. See ek9 -h E50001 for details.


Question: Find the highest bid from two optional auction offers.

How do I select the maximum of two Money values when one bidder might not have bid?

An auction receives two bids but one could be missing — pick the higher one safely.

In Java I'd need null checks before comparing BigDecimal bids. What does EK9 use?

Migrating from Python where I use max() with None filtering for prices — what is the EK9 pattern?

Answer:
The >? coalescing maximum operator returns the greater of two Money values, handling unset gracefully.

  higherBid() as pure
    -> left as Money, right as Money
    <- rtn as Money: left >? right

If both bids are set, >? returns the higher one. If one is unset (no bid placed), it returns the available bid. If both are unset, the result is unset.

This is a COMPARISON operator — it picks the larger value. Do not confuse it with :=? (guarded assignment), which assigns only if the target is unset. See Q1226 for a side-by-side contrast.

See Q1092 for min/max coalescing overview. See Q1209 for <? minimum on Money.

defines module qa.operators.coalescemax.money

  defines function

    higherBid() as pure
      ->
        left as Money
        right as Money
      <- rtn as Money: left >? right

  defines program

    CoalesceMaxMoneyDemo()
      stdout <- Stdout()

      // === BOTH BIDS RECEIVED — returns the higher ===

      bidderAlpha <- 5000.00#USD
      bidderBravo <- 7500.00#USD
      winningBid <- higherBid(bidderAlpha, bidderBravo)
      stdout.println(`Winning bid: ${winningBid}`)

      // === ONE BIDDER DIDN'T BID — returns the available bid ===

      activeBid <- 3200.00#USD
      missingBid <- Money()
      onlyBid <- higherBid(activeBid, missingBid)
      stdout.println(`Only bid: ${onlyBid}`)

      // === NO BIDS — result is unset ===

      missingA <- Money()
      missingB <- Money()
      bothMissing <- higherBid(missingA, missingB)
      if bothMissing?
        stdout.println(`Bid: ${bothMissing}`)
      else
        stdout.println("No bids received")

Java: a == null ? b : b == null ? a : a.compareTo(b) >= 0 ? a : b. Python: max(b for b in [a, b] if b is not None). Rust: no built-in money. EK9: left >? right — one operator handles all cases.

>? auction bid coalescing higher maximum money offer

>? coalescing maximum
money comparison
auction bidding

Explanation: Money has no max() method; use the >? coalescing-maximum operator, which handles unset values and returns the greater. See ek9 -h E50060 for details.


Question: Determine the latest expiry date from two optional certificates.

How do I find the maximum of two Date values when one certificate might not exist?

A system has two TLS certificates but one might be missing — find the latest expiry.

In Java I'd need null checks and isAfter to find the latest date. What does EK9 use?

Migrating from Go where I check nil before comparing dates — what is the EK9 pattern?

Answer:
The >? coalescing maximum operator returns the later of two Date values, handling unset gracefully.

  latestExpiry() as pure
    -> left as Date, right as Date
    <- rtn as Date: left >? right

If both dates are set, >? returns the later one. If one is unset (certificate missing), it returns the available date. If both are unset, the result is unset.

This is a COMPARISON operator — it picks the larger value. Do not confuse it with :=? (guarded assignment), which assigns only if the target is unset. See Q1226 for a side-by-side contrast.

See Q1092 for min/max coalescing overview. See Q1208 for <? minimum on Date.

defines module qa.operators.coalescemax.date

  defines function

    latestExpiry() as pure
      ->
        left as Date
        right as Date
      <- rtn as Date: left >? right

  defines program

    CoalesceMaxDateDemo()
      stdout <- Stdout()

      // === BOTH CERTIFICATES PRESENT — returns the later expiry ===

      certA <- 2025-06-15
      certB <- 2026-01-20
      latestDate <- latestExpiry(certA, certB)
      stdout.println(`Latest expiry: ${latestDate}`)

      // === ONE CERTIFICATE MISSING — returns the available one ===

      activeCert <- 2025-09-30
      missingCert <- Date()
      onlyExpiry <- latestExpiry(activeCert, missingCert)
      stdout.println(`Available cert expires: ${onlyExpiry}`)

      // === BOTH MISSING — result is unset ===

      noCertA <- Date()
      noCertB <- Date()
      noExpiry <- latestExpiry(noCertA, noCertB)
      if noExpiry?
        stdout.println(`Expiry: ${noExpiry}`)
      else
        stdout.println("No certificates found")

Java: a == null ? b : b == null ? a : a.isAfter(b) ? a : b. Python: max(d for d in [a, b] if d is not None). Kotlin: listOfNotNull(a, b).maxOrNull(). EK9: left >? right — one operator handles all cases.

>? TLS certificate coalescing date expiry latest maximum

>? coalescing maximum
date comparison
certificate expiry

Explanation: Date has no isAfter() method, so left.isAfter(right) is unresolved — use the >? coalescing maximum operator instead. See ek9 -h E50060 for details.


Question: Select the longer warranty period from two product options.

How do I find the maximum of two Duration values when one product has no warranty info?

Two product variants have different warranty periods but one might be unspecified — pick the longer.

In Java I'd need null checks before comparing Duration objects. What does EK9 use?

Migrating from C# where I use TimeSpan comparison with nullable checks — what is the EK9 pattern?

Answer:
The >? coalescing maximum operator returns the longer of two Duration values, handling unset gracefully.

  longerWarranty() as pure
    -> left as Duration, right as Duration
    <- rtn as Duration: left >? right

If both durations are set, >? returns the longer one. If one is unset (no warranty info), it returns the available warranty. If both are unset, the result is unset.

This is a COMPARISON operator — it picks the larger value. Do not confuse it with :=? (guarded assignment), which assigns only if the target is unset. See Q1226 for a side-by-side contrast.

See Q1092 for min/max coalescing overview. See Q1210 for <? minimum on Duration.

defines module qa.operators.coalescemax.duration

  defines function

    longerWarranty() as pure
      ->
        left as Duration
        right as Duration
      <- rtn as Duration: left >? right

  defines program

    CoalesceMaxDurationDemo()
      stdout <- Stdout()

      // === BOTH WARRANTIES KNOWN — returns the longer ===

      standardWarranty <- P1Y
      extendedWarranty <- P3Y
      bestWarranty <- longerWarranty(standardWarranty, extendedWarranty)
      stdout.println(`Best warranty: ${bestWarranty}`)

      // === ONE WARRANTY UNKNOWN — returns the available one ===

      knownWarranty <- P2Y
      unknownWarranty <- Duration()
      onlyWarranty <- longerWarranty(knownWarranty, unknownWarranty)
      stdout.println(`Available warranty: ${onlyWarranty}`)

      // === BOTH UNKNOWN — result is unset ===

      missingA <- Duration()
      missingB <- Duration()
      noWarranty <- longerWarranty(missingA, missingB)
      if noWarranty?
        stdout.println(`Warranty: ${noWarranty}`)
      else
        stdout.println("No warranty information available")

Java: a == null ? b : b == null ? a : a.compareTo(b) >= 0 ? a : b. Python: max(d for d in [a, b] if d is not None). Go: custom function with nil checks on time.Duration. EK9: left >? right — one operator handles all cases.

>? coalescing duration longer maximum period product warranty

>? coalescing maximum
duration comparison
warranty selection


Question: Get the higher score from two optional exam results.

How do I find the maximum of two Integer values when one student might not have taken the exam?

A grading system has two exam scores but one might be absent — pick the higher score.

In Java I'd need Optional and Math.max for nullable integers. What does EK9 use?

Migrating from Python where I use max() with None filtering — what is the EK9 pattern?

Answer:
The >? coalescing maximum operator returns the greater of two Integer values, handling unset gracefully.

  higherScore() as pure
    -> left as Integer, right as Integer
    <- rtn as Integer: left >? right

If both scores are set, >? returns the higher one. If one is unset (exam not taken), it returns the available score. If both are unset, the result is unset.

This is a COMPARISON operator — it picks the larger value. Do not confuse it with :=? (guarded assignment), which assigns only if the target is unset. See Q1226 for a side-by-side contrast.

See Q1092 for min/max coalescing overview. See Q1212 for <? minimum on Integer.

defines module qa.operators.coalescemax.integer

  defines function

    higherScore() as pure
      ->
        left as Integer
        right as Integer
      <- rtn as Integer: left >? right

  defines program

    CoalesceMaxIntegerDemo()
      stdout <- Stdout()

      // === BOTH EXAMS TAKEN — returns the higher score ===

      midtermScore <- 78
      practicalScore <- 85
      bestScore <- higherScore(midtermScore, practicalScore)
      stdout.println(`Best score: ${bestScore}`)

      // === ONE EXAM NOT TAKEN — returns the available score ===

      writtenScore <- 92
      absentScore <- Integer()
      onlyScore <- higherScore(writtenScore, absentScore)
      stdout.println(`Available score: ${onlyScore}`)

      // === BOTH EXAMS MISSED — result is unset ===

      missedA <- Integer()
      missedB <- Integer()
      noScore <- higherScore(missedA, missedB)
      if noScore?
        stdout.println(`Score: ${noScore}`)
      else
        stdout.println("No exam scores recorded")

Java: a == null ? b : b == null ? a : Math.max(a, b). Python: max(s for s in [a, b] if s is not None). Go: custom function with nil checks. EK9: left >? right — one operator handles all cases.

>? coalescing exam grading higher integer maximum score

>? coalescing maximum
integer comparison
exam scoring

<- rtn as Integer: Math.max(left, right)

Explanation: EK9 has no 'Math.max' and the 'Math' reference does not resolve; use the '>?' coalescing maximum operator instead. See ek9 -h E50001 for details.


Question: Select the earlier-or-equal time from two shift starts.

How do I find the lesser-or-equal of two Time values when one shift might be unscheduled?

Two shifts have start times but one could be empty — pick the earlier or equal time.

In Java I'd need null checks before comparing with isBefore or equals. What does EK9 use?

Migrating from Python where I use min() for times — does EK9 have a less-than-or-equal coalescing?

Answer:
The <=? coalescing operator returns the lesser-or-equal of two values, handling unset gracefully.

  earlierOrEqual() as pure
    -> left as Time, right as Time
    <- rtn as Time: left <=? right

If both times are set and different, <=? returns the earlier one (same as <?). If both are equal, it returns that value. If one is unset, it returns the set one. If both unset, the result is unset.

The difference from <? appears when values are equal: <=? and <? both return the same result, but <=? semantically includes the equality case. The full coalescing family is <?, >?, <=?, >=?. See Q1227 for all four operators side by side.

Do not confuse any coalescing operator with :=? (guarded assignment). See Q1226 for the contrast.

defines module qa.operators.coalescelte.time

  defines function

    earlierOrEqual() as pure
      ->
        left as Time
        right as Time
      <- rtn as Time: left <=? right

  defines program

    CoalesceLteTimeDemo()
      stdout <- Stdout()

      // === BOTH SHIFTS SCHEDULED — returns the earlier ===

      morningShift <- 06:00
      dayShift <- 09:00
      firstStart <- earlierOrEqual(morningShift, dayShift)
      stdout.println(`First shift starts: ${firstStart}`)

      // === EQUAL TIMES — returns the shared time ===

      shiftA <- 08:00
      shiftB <- 08:00
      sameStart <- earlierOrEqual(shiftA, shiftB)
      stdout.println(`Both start at: ${sameStart}`)

      // === ONE SHIFT UNSCHEDULED — returns the scheduled one ===

      scheduledShift <- 07:30
      unscheduledShift <- Time()
      onlyShift <- earlierOrEqual(scheduledShift, unscheduledShift)
      stdout.println(`Scheduled shift: ${onlyShift}`)

      // === BOTH UNSCHEDULED — result is unset ===

      missingShift <- Time()
      noShiftB <- Time()
      bothMissing <- earlierOrEqual(missingShift, noShiftB)
      if bothMissing?
        stdout.println(`Shift: ${bothMissing}`)
      else
        stdout.println("No shifts scheduled")

Java: a == null ? b : b == null ? a : !a.isAfter(b) ? a : b. Python: min(a, b) handles equality naturally. Kotlin: listOfNotNull(a, b).minOrNull(). EK9: left <=? right — one operator handles all cases including equality.

<=? coalescing earlier equal less-or-equal schedule shift time

<=? coalescing less-or-equal
time comparison
shift scheduling

Explanation: '<=' is a comparison that yields Boolean, so assigning 'left <= right' to a Time return is a type mismatch; use the coalescing '<=?' which returns the lesser-or-equal Time and handles unset values. See ek9 -h E50030 for details.


Question: Get the higher-or-equal temperature threshold.

How do I find the greater-or-equal of two Float values when one threshold might be unset?

Two temperature thresholds are defined but one might be missing — pick the higher or equal.

In Java I'd need null checks before Math.max with nullable doubles. What does EK9 use?

Migrating from Go where I compare float64 with nil checks — does EK9 have a greater-or-equal coalescing?

Answer:
The >=? coalescing operator returns the greater-or-equal of two values, handling unset gracefully.

  higherOrEqual() as pure
    -> left as Float, right as Float
    <- rtn as Float: left >=? right

If both values are set and different, >=? returns the larger one (same as >?). If both are equal, it returns that value. If one is unset, it returns the set one. If both unset, the result is unset.

The full coalescing family is <?, >?, <=?, >=?. See Q1227 for all four operators side by side.

Do not confuse any coalescing operator with :=? (guarded assignment). See Q1226 for the contrast.

defines module qa.operators.coalescegte.float

  defines function

    higherOrEqual() as pure
      ->
        left as Float
        right as Float
      <- rtn as Float: left >=? right

  defines program

    CoalesceGteFloatDemo()
      stdout <- Stdout()

      // === BOTH THRESHOLDS SET — returns the higher ===

      warningThreshold <- 35.0
      criticalThreshold <- 40.0
      upperLimit <- higherOrEqual(warningThreshold, criticalThreshold)
      stdout.println(`Upper threshold: ${upperLimit}`)

      // === EQUAL THRESHOLDS — returns the shared value ===

      limitA <- 37.5
      limitB <- 37.5
      sameLimit <- higherOrEqual(limitA, limitB)
      stdout.println(`Shared threshold: ${sameLimit}`)

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

      configuredLimit <- 42.0
      missingLimit <- Float()
      onlyLimit <- higherOrEqual(configuredLimit, missingLimit)
      stdout.println(`Configured threshold: ${onlyLimit}`)

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

      absentFirst <- Float()
      absentSecond <- Float()
      bothMissing <- higherOrEqual(absentFirst, absentSecond)
      if bothMissing?
        stdout.println(`Threshold: ${bothMissing}`)
      else
        stdout.println("No temperature threshold configured")

Java: a == null ? b : b == null ? a : a >= b ? a : b. Python: max(a, b) handles equality naturally. Kotlin: listOfNotNull(a, b).maxOrNull(). EK9: left >=? right — one operator handles all cases including equality.

>=? coalescing equal float greater-or-equal higher temperature threshold

>=? coalescing greater-or-equal
float comparison
threshold selection


Question: Set a default user name only if no name has been provided.

How do I conditionally assign a String only when the variable is unset?

A registration form has an optional name field — apply a default guest name if empty.

In Java I'd check if name == null before assigning. What does EK9 use?

Migrating from Python where I use 'name = name or default' — what is the EK9 pattern?

Answer:
The :=? guarded assignment operator assigns a value ONLY if the target variable is currently unset. If the variable already has a value, the assignment is skipped.

  name <- String()
  name :=? "Guest"
  //name is now "Guest" because it was unset

  name2 <- "Alice"
  name2 :=? "Guest"
  //name2 is still "Alice" — the :=? was skipped

This is an ASSIGNMENT operator — it sets a variable.

See Q1038 for :=? with config fallback chains.

defines module qa.flow.guardassign.string

  defines function

    lookupUserName()
      <- rtn <- String()
      //Simulates: no user name found in database

    lookupDisplayName()
      <- rtn <- "RegisteredUser"

  defines program

    GuardAssignStringDemo()
      stdout <- Stdout()

      // === VARIABLE IS UNSET — :=? assigns the default ===

      userName <- String()
      defaultName <- "Guest"
      userName :=? defaultName
      stdout.println(`User name: ${userName}`)

      // === VARIABLE IS ALREADY SET — :=? is skipped ===

      knownName <- lookupUserName()
      knownName :=? "Guest"
      stdout.println(`Known name preserved: ${knownName}`)

      // === FALLBACK CHAIN — first set value wins ===

      displayName <- String()
      displayName :=? lookupUserName()
      displayName :=? lookupDisplayName()
      displayName :=? "Anonymous"
      stdout.println(`Display name: ${displayName}`)

      // === CONTRAST WITH := (regular assignment) ===
      // Regular := always overwrites
      // Guarded :=? only assigns when unset

      greeting <- lookupDisplayName()
      greeting :=? "Hi"
      stdout.println(`Greeting preserved: ${greeting}`)

Java: if (name == null) name = "Guest". Python: name = name or "Guest" (but fails for empty string). Go: if name == "" { name = "Guest" }. Kotlin: name = name ?: "Guest". EK9: name :=? "Guest" — one operator, correct tri-state semantics.

:=? assignment conditional default guarded name string unset

:=? guarded assignment
string defaults
conditional initialization

      knownName <- "Alice"
      knownName :=? "Guest"

      knownName <- "Alice"
      if knownName == null
        knownName := "Guest"

Explanation: EK9 has no null — writing '== null' references an unsupported literal; use the guarded assignment ':=?' to set a value only when the variable is currently unset. See ek9 -h E01073 for details.


Question: Apply a default port number only when the config port is unset.

How do I conditionally assign an Integer only when the variable is unset?

A server config has an optional port — apply the default 8080 if not configured.

In Java I'd check if port == null before assigning a default. What does EK9 use?

Migrating from Go where I check if port == 0 — what is the EK9 pattern for default integers?

Answer:
The :=? guarded assignment operator assigns a value ONLY if the target variable is currently unset. If the variable already has a value, the assignment is skipped.

  port <- Integer()
  defaultPort <- 8080
  port :=? defaultPort
  //port is now 8080 because it was unset

This is an ASSIGNMENT operator — it sets a variable.

See Q1038 for :=? with config fallback chains.

defines module qa.flow.guardassign.integer

  defines function

    loadPortFromEnvironment()
      <- rtn <- Integer()
      //Simulates: no port configured in environment

    loadPortFromConfigFile()
      <- rtn <- 9090

  defines program

    GuardAssignIntegerDemo()
      stdout <- Stdout()

      // === PORT IS UNSET — :=? assigns the default ===

      port <- Integer()
      defaultPort <- 8080
      port :=? defaultPort
      stdout.println(`Server port: ${port}`)

      // === PORT IS ALREADY CONFIGURED — :=? is skipped ===

      customPort <- loadPortFromConfigFile()
      customPort :=? defaultPort
      stdout.println(`Custom port preserved: ${customPort}`)

      // === FALLBACK CHAIN — first set value wins ===

      serverPort <- Integer()
      serverPort :=? loadPortFromEnvironment()
      serverPort :=? loadPortFromConfigFile()
      fallbackPort <- 8080
      serverPort :=? fallbackPort
      stdout.println(`Resolved port: ${serverPort}`)

Java: if (port == null) port = 8080. Python: port = port or 8080 (but fails for port 0). Go: if port == 0 { port = 8080 } (but 0 might be valid). Kotlin: port = port ?: 8080. EK9: port :=? defaultPort — correct tri-state semantics, 0 is a valid set value.

:=? assignment config default guarded integer port server

:=? guarded assignment
integer defaults
server configuration

      if port == null
        port := defaultPort

Explanation: EK9 has no null. Use :=? to conditionally assign — it only sets the value when the variable is currently unset. See ek9 -h E01072 for details.


Question: Set a fallback deadline only if no deadline was specified.

How do I conditionally assign a Date only when the variable is unset?

A project has an optional deadline — apply a default end-of-quarter date if missing.

In Java I'd check if deadline == null before assigning. What does EK9 use?

Migrating from Kotlin where I use the elvis operator for nullable dates — what is the EK9 pattern?

Answer:
The :=? guarded assignment operator assigns a value ONLY if the target variable is currently unset. If the variable already has a value, the assignment is skipped.

  deadline <- Date()
  fallbackDate <- 2024-12-31
  deadline :=? fallbackDate
  //deadline is now 2024-12-31 because it was unset

This is an ASSIGNMENT operator — it sets a variable.

See Q1038 for :=? with config fallback chains.

defines module qa.flow.guardassign.date

  defines function

    lookupProjectDeadline()
      <- rtn <- Date()
      //Simulates: no deadline set for the project

    lookupSprintEnd()
      <- rtn <- 2024-09-30

  defines program

    GuardAssignDateDemo()
      stdout <- Stdout()

      // === DEADLINE IS UNSET — :=? assigns the fallback ===

      deadline <- Date()
      fallbackDate <- 2024-12-31
      deadline :=? fallbackDate
      stdout.println(`Deadline: ${deadline}`)

      // === DEADLINE IS ALREADY SET — :=? is skipped ===

      existingDeadline <- lookupSprintEnd()
      existingDeadline :=? fallbackDate
      stdout.println(`Existing deadline preserved: ${existingDeadline}`)

      // === FALLBACK CHAIN — first set value wins ===

      projectEnd <- Date()
      projectEnd :=? lookupProjectDeadline()
      projectEnd :=? lookupSprintEnd()
      endOfYear <- 2024-12-31
      projectEnd :=? endOfYear
      stdout.println(`Project end: ${projectEnd}`)

Java: if (deadline == null) deadline = LocalDate.of(2024, 12, 31). Python: deadline = deadline or default_date. Kotlin: deadline = deadline ?: defaultDate. Go: if deadline.IsZero() { deadline = fallback }. EK9: deadline :=? fallbackDate — one operator, correct tri-state semantics.

:=? assignment date deadline default fallback guarded project

:=? guarded assignment
date defaults
project deadline

      if deadline == null
        deadline := fallbackDate


Question: Apply a minimum price only if the product price hasn't been set.

How do I conditionally assign a Money value only when the variable is unset?

A product listing has an optional price — apply a default minimum if not set.

In Java I'd check if price == null before assigning a BigDecimal default. What does EK9 use?

Migrating from Python where I use 'price = price or Decimal(default)' — what is the EK9 pattern?

Answer:
The :=? guarded assignment operator assigns a value ONLY if the target variable is currently unset. If the variable already has a value, the assignment is skipped.

  price <- Money()
  minimumPrice <- 9.99#USD
  price :=? minimumPrice
  //price is now 9.99 USD because it was unset

This is an ASSIGNMENT operator — it sets a variable.

See Q1038 for :=? with config fallback chains.

defines module qa.flow.guardassign.money

  defines function

    lookupCatalogPrice()
      <- rtn <- Money()
      //Simulates: no catalog price found

    lookupSuggestedPrice()
      <- rtn <- 24.99#USD

  defines program

    GuardAssignMoneyDemo()
      stdout <- Stdout()

      // === PRICE IS UNSET — :=? assigns the minimum ===

      price <- Money()
      minimumPrice <- 9.99#USD
      price :=? minimumPrice
      stdout.println(`Product price: ${price}`)

      // === PRICE IS ALREADY SET — :=? is skipped ===

      existingPrice <- 49.99#USD
      existingPrice :=? minimumPrice
      stdout.println(`Existing price preserved: ${existingPrice}`)

      // === FALLBACK CHAIN — first set value wins ===

      listPrice <- Money()
      listPrice :=? lookupCatalogPrice()
      listPrice :=? lookupSuggestedPrice()
      listPrice :=? minimumPrice
      stdout.println(`List price: ${listPrice}`)

Java: if (price == null) price = new BigDecimal("9.99"). Python: price = price or Decimal('9.99') (fails for Decimal(0)). Kotlin: price = price ?: minimumPrice. Go: no built-in money type. EK9: price :=? minimumPrice — one operator, correct tri-state semantics.

:=? assignment default guarded minimum money price product

:=? guarded assignment
money defaults
product pricing

      if price == null
        price := minimumPrice


Question: Set a default tax rate only when the rate hasn't been configured.

How do I conditionally assign a Float only when the variable is unset?

A tax calculator has an optional rate — apply the standard rate if not configured.

In Java I'd check if taxRate == null before assigning a double default. What does EK9 use?

Migrating from Go where I check if rate == 0.0 — what is the EK9 pattern for default floats?

Answer:
The :=? guarded assignment operator assigns a value ONLY if the target variable is currently unset. If the variable already has a value, the assignment is skipped.

  taxRate <- Float()
  defaultRate <- 0.20
  taxRate :=? defaultRate
  //taxRate is now 0.20 because it was unset

This is an ASSIGNMENT operator — it sets a variable.

See Q1038 for :=? with config fallback chains.

defines module qa.flow.guardassign.float

  defines function

    lookupRegionalRate()
      <- rtn <- Float()
      //Simulates: no regional tax rate configured

    lookupNationalRate()
      <- rtn <- 0.15

  defines program

    GuardAssignFloatDemo()
      stdout <- Stdout()

      // === RATE IS UNSET — :=? assigns the default ===

      taxRate <- Float()
      defaultRate <- 0.20
      taxRate :=? defaultRate
      stdout.println(`Tax rate: ${taxRate}`)

      // === RATE IS ALREADY CONFIGURED — :=? is skipped ===

      customRate <- lookupNationalRate()
      customRate :=? defaultRate
      stdout.println(`Custom rate preserved: ${customRate}`)

      // === FALLBACK CHAIN — first set value wins ===

      effectiveRate <- Float()
      effectiveRate :=? lookupRegionalRate()
      effectiveRate :=? lookupNationalRate()
      effectiveRate :=? defaultRate
      stdout.println(`Effective rate: ${effectiveRate}`)

Java: if (taxRate == null) taxRate = 0.20. Python: tax_rate = tax_rate or 0.20 (fails for rate 0.0). Go: if rate == 0.0 { rate = 0.20 } (but 0.0 might be valid). EK9: taxRate :=? defaultRate — correct tri-state semantics, 0.0 is a valid set value.

:=? assignment configuration default float guarded rate tax

:=? guarded assignment
float defaults
tax configuration

      if taxRate == null
        taxRate := defaultRate


Question: Apply a default session timeout only if no timeout was set.

How do I conditionally assign a Duration only when the variable is unset?

A web application has an optional session timeout — apply a default if not configured.

In Java I'd check if timeout == null before assigning a Duration default. What does EK9 use?

Migrating from C# where I use '??' for nullable TimeSpan — what is the EK9 pattern?

Answer:
The :=? guarded assignment operator assigns a value ONLY if the target variable is currently unset. If the variable already has a value, the assignment is skipped.

  timeout <- Duration()
  defaultTimeout <- PT30M
  timeout :=? defaultTimeout
  //timeout is now 30 minutes because it was unset

This is an ASSIGNMENT operator — it sets a variable.

See Q1038 for :=? with config fallback chains.

defines module qa.flow.guardassign.duration

  defines function

    loadTimeoutFromConfig()
      <- rtn <- Duration()
      //Simulates: no timeout in configuration

    loadTimeoutFromProfile()
      <- rtn <- PT15M

  defines program

    GuardAssignDurationDemo()
      stdout <- Stdout()

      // === TIMEOUT IS UNSET — :=? assigns the default ===

      timeout <- Duration()
      defaultTimeout <- PT30M
      timeout :=? defaultTimeout
      stdout.println(`Session timeout: ${timeout}`)

      // === TIMEOUT IS ALREADY CONFIGURED — :=? is skipped ===

      customTimeout <- PT1H
      customTimeout :=? defaultTimeout
      stdout.println(`Custom timeout preserved: ${customTimeout}`)

      // === FALLBACK CHAIN — first set value wins ===

      sessionTimeout <- Duration()
      sessionTimeout :=? loadTimeoutFromConfig()
      sessionTimeout :=? loadTimeoutFromProfile()
      sessionTimeout :=? defaultTimeout
      stdout.println(`Effective timeout: ${sessionTimeout}`)

Java: if (timeout == null) timeout = Duration.ofMinutes(30). Python: timeout = timeout or timedelta(minutes=30). C#: timeout = timeout ?? TimeSpan.FromMinutes(30). Go: if timeout == 0 { timeout = 30 * time.Minute }. EK9: timeout :=? defaultTimeout — one operator, correct tri-state semantics.

:=? assignment default duration guarded session timeout web

:=? guarded assignment
duration defaults
session timeout

      if timeout == null
        timeout := defaultTimeout


Question: Demonstrate the difference between <? coalescing and :=? guarded assignment.

What is the distinction between <? and :=? in EK9?

I keep confusing <? coalescing with :=? guarded assignment — show them side by side.

In Java I mix up Math.min with null-check assignment. How are these separate in EK9?

Show the two operators that look similar but do completely different things.

Answer:
<? and :=? are DIFFERENT operations despite the similar ? suffix.

<? is a COMPARISON — it returns the lesser of two values:
  cheaperOf() as pure
    -> left as Money, right as Money
    <- rtn as Money: left <? right
  //Returns the smaller Money value, handles unset

:=? is an ASSIGNMENT — it sets a variable only if unset:
  price <- Money()
  price :=? 9.99#USD
  //Assigns 9.99 USD because price was unset

KEY DIFFERENCES:
- <? compares two values and produces a result. It goes inside pure functions.
- :=? modifies a variable in place. It is an assignment, not a comparison.
- <? has a companion >? (coalescing maximum). :=? has no companion.
- <? always evaluates both sides. :=? skips the right side if the target is already set.

See Q1092 for <? and >? coalescing. See Q1038 for :=? fallback chains. See Q1227 for all four coalescing operators.

defines module qa.operators.coalescevsguard

  defines function

    // <? is a COMPARISON — used inside pure functions
    cheaperOf() as pure
      ->
        left as Money
        right as Money
      <- rtn as Money: left <? right

    earlierOf() as pure
      ->
        left as Date
        right as Date
      <- rtn as Date: left <? right

  defines program

    CoalesceVsGuardDemo()
      stdout <- Stdout()

      // ============================================================
      // PART 1: <? COALESCING MINIMUM — compares two values
      // ============================================================

      // <? returns the LESSER of two values
      vendorA <- 149.99#USD
      vendorB <- 129.50#USD
      bestPrice <- cheaperOf(vendorA, vendorB)
      stdout.println(`Cheaper quote (both set): ${bestPrice}`)

      // <? with one unset — returns the set value
      knownQuote <- 199.00#USD
      missingQuote <- Money()
      availableQuote <- cheaperOf(knownQuote, missingQuote)
      stdout.println(`Available quote (one unset): ${availableQuote}`)

      // ============================================================
      // PART 2: :=? GUARDED ASSIGNMENT — sets a variable if unset
      // ============================================================

      // :=? assigns ONLY IF the target is unset
      price <- Money()
      minimumPrice <- 9.99#USD
      price :=? minimumPrice
      stdout.println(`Default price applied: ${price}`)

      // :=? with already-set target — assignment is skipped
      existingPrice <- 49.99#USD
      existingPrice :=? minimumPrice
      stdout.println(`Existing price preserved: ${existingPrice}`)

      // ============================================================
      // PART 3: SAME TYPES, DIFFERENT OPERATIONS
      // ============================================================

      // Both operations work on Date, but do different things:

      // <? picks the earlier of two dates
      dateA <- 2024-11-15
      dateB <- 2024-11-08
      earlierDate <- earlierOf(dateA, dateB)
      stdout.println(`Earlier date: ${earlierDate}`)

      // :=? sets a deadline only if none exists
      deadline <- Date()
      fallbackDate <- 2024-12-31
      deadline :=? fallbackDate
      stdout.println(`Fallback deadline: ${deadline}`)

Java conflates these: Math.min(a, b) for comparison vs if (x == null) x = default for assignment — both need null checks. EK9 separates them cleanly: <? for comparison, :=? for conditional assignment. Each handles unset values automatically.

:=? <? assignment coalescing comparison contrast distinction guarded

<? vs :=? distinction
coalescing comparison
guarded assignment


Question: Show all four coalescing comparison operators on Integer values.

What are all the coalescing operators in EK9 and how do they differ?

I need to see <?, >?, <=?, >=? side by side on the same type.

In Java there's no equivalent to coalescing operators — show the full EK9 family.

Demonstrate the complete set of coalescing comparison operators with Integer examples.

Answer:
EK9 has four coalescing comparison operators. All handle unset values gracefully.

  minOf() as pure
    -> left as Integer, right as Integer
    <- rtn as Integer: left <? right     //lesser
  maxOf() as pure
    -> left as Integer, right as Integer
    <- rtn as Integer: left >? right     //greater
  minOrEqual() as pure
    -> left as Integer, right as Integer
    <- rtn as Integer: left <=? right    //lesser-or-equal
  maxOrEqual() as pure
    -> left as Integer, right as Integer
    <- rtn as Integer: left >=? right    //greater-or-equal

All four are COMPARISON operators — they compare two values and return a result. They are NOT assignment operators. Do not confuse them with :=? (guarded assignment), which is a completely different operation.

See Q1226 for the distinction between <? and :=?. See Q1092 for min/max coalescing basics.

defines module qa.operators.allcoalescing

  defines function

    // <? — returns the LESSER of two values
    minOf() as pure
      ->
        left as Integer
        right as Integer
      <- rtn as Integer: left <? right

    // >? — returns the GREATER of two values
    maxOf() as pure
      ->
        left as Integer
        right as Integer
      <- rtn as Integer: left >? right

    // <=? — returns the lesser-or-equal of two values
    minOrEqual() as pure
      ->
        left as Integer
        right as Integer
      <- rtn as Integer: left <=? right

    // >=? — returns the greater-or-equal of two values
    maxOrEqual() as pure
      ->
        left as Integer
        right as Integer
      <- rtn as Integer: left >=? right

  defines program

    AllCoalescingOperatorsDemo()
      stdout <- Stdout()

      // === ALL FOUR OPERATORS WITH BOTH VALUES SET ===

      scoreA <- 78
      scoreB <- 92

      minResult <- minOf(scoreA, scoreB)
      stdout.println(`<? minimum: ${minResult}`)

      maxResult <- maxOf(scoreA, scoreB)
      stdout.println(`>? maximum: ${maxResult}`)

      lessOrEqualResult <- minOrEqual(scoreA, scoreB)
      stdout.println(`<=? lesser-or-equal: ${lessOrEqualResult}`)

      greaterOrEqualResult <- maxOrEqual(scoreA, scoreB)
      stdout.println(`>=? greater-or-equal: ${greaterOrEqualResult}`)

      // === WITH EQUAL VALUES ===

      valueA <- 50
      valueB <- 50

      equalMin <- minOf(valueA, valueB)
      stdout.println(`<? with equal values: ${equalMin}`)

      equalMax <- maxOf(valueA, valueB)
      stdout.println(`>? with equal values: ${equalMax}`)

      // === WITH ONE UNSET — all return the set value ===

      activeReading <- 65
      offlineReading <- Integer()

      minUnset <- minOf(activeReading, offlineReading)
      stdout.println(`<? one unset: ${minUnset}`)

      maxUnset <- maxOf(activeReading, offlineReading)
      stdout.println(`>? one unset: ${maxUnset}`)

      lessEqualWhenUnset <- minOrEqual(activeReading, offlineReading)
      stdout.println(`<=? one unset: ${lessEqualWhenUnset}`)

      greaterEqualWhenUnset <- maxOrEqual(activeReading, offlineReading)
      stdout.println(`>=? one unset: ${greaterEqualWhenUnset}`)

      // === WITH BOTH UNSET — all return unset ===

      missingA <- Integer()
      missingB <- Integer()

      noneMin <- minOf(missingA, missingB)
      if noneMin?
        stdout.println(`Unexpected: ${noneMin}`)
      else
        stdout.println("<? both unset: result is unset")

Java: requires 4 separate utility methods with null checks. Python: min/max handle None only with filtering. Kotlin: requires custom extension functions. EK9: <?, >?, <=?, >=? — four built-in operators that handle unset values automatically.

<=? <? >=? >? all coalescing comparison family integer

coalescing operator family
<?
>?
<=?
>=?


Question: Find the cheaper Product from two optional product records.

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?

Answer:
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.

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}`)

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.

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

<? coalescing on records
default operator enables coalescing
user-defined type comparison

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


Question: Select the higher-priority Task from two optional task records.

How do I pick the task with the higher priority using coalescing operators?

Given two optional Task records with priority numbers, choose the more urgent one.

In Java I'd use Comparator.comparing(Task::getPriority) to find min priority. What does EK9 use?

Migrating from Python where I use min(tasks, key=lambda t: t.priority) — what is the EK9 equivalent?

Answer:
Define a custom <=> operator that compares only the priority field. Lower priority number means higher priority, so use <? to pick the more urgent task.

  higherPriority() as pure
    -> left as Task, right as Task
    <- rtn as Task: left <? right

The custom <=> lets you control which field determines ordering. The <? coalescing operator uses <=> to find the lesser value — and since lower priority number means higher urgency, <? picks the more urgent task.

See Q1228 for <? with default operator. See Q1049 for custom comparison operators.

defines module qa.operators.coalescemax.record

  defines record

    Task
      title as String: String()
      priority as Integer: 0

      Task()
        ->
          title as String
          priority as Integer
        this.title: title
        this.priority: priority

      operator <=> as pure
        -> other as Task
        <- rtn as Integer: priority <=> other.priority

      default operator

  defines function

    higherPriority() as pure
      ->
        left as Task
        right as Task
      <- rtn as Task: left <? right

  defines program

    CoalesceMaxRecordDemo()
      stdout <- Stdout()

      // === BOTH SET — <? returns the lesser priority number (higher urgency) ===

      criticalTask <- Task("Fix outage", 1)
      normalTask <- Task("Update docs", 5)
      moreUrgent <- higherPriority(criticalTask, normalTask)
      stdout.println(`More urgent: ${moreUrgent}`)

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

      knownTask <- Task("Deploy release", 3)
      unknownTask <- Task()
      available <- higherPriority(knownTask, unknownTask)
      stdout.println(`Available task: ${available}`)

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

      pendingA <- Task()
      pendingB <- Task()
      noTask <- higherPriority(pendingA, pendingB)
      if noTask?
        stdout.println(`Task: ${noTask}`)
      else
        stdout.println("No task available")

      // === USE >? FOR LOWEST PRIORITY (largest number) ===

      lowest <- criticalTask >? normalTask
      stdout.println(`Lowest priority: ${lowest}`)

Java: Collections.min(tasks, Comparator.comparingInt(Task::getPriority)). Python: min(tasks, key=lambda t: t.priority). Kotlin: tasks.minByOrNull { it.priority }. EK9: left <? right with custom <=> on priority field.

<?
coalescing
comparison
custom operator
minimum
priority
record
task

<? with custom <=>
priority comparison
custom operator ordering

      operator <=> as pure
        -> other as Task
        <- rtn as Boolean: priority < other.priority

Explanation: The <=> operator must return Integer, not Boolean. It returns negative, zero, or positive to indicate ordering. See ek9 -h E07550 for details.


Question: Sort two Measurement records by value and pick the smaller using <? coalescing.

How do I coalesce custom records that compare by a specific field?

Given two sensor measurements, select the one with the lower reading.

In Java I'd use Comparator.comparingDouble for a single field. What does EK9 offer?

Migrating from Rust where I derive Ord on a struct — how does EK9 do custom ordering?

Answer:
Define a custom <=> operator that compares only the reading field. The <? coalescing operator then uses that ordering.

  operator <=> as pure
    -> other as Measurement
    <- rtn as Integer: reading <=> other.reading

  smallerReading() as pure
    -> left as Measurement, right as Measurement
    <- rtn as Measurement: left <? right

The custom <=> means <? compares by reading only, ignoring label. This is different from 'default operator' which would compare label first (alphabetically), then reading.

See Q1228 for default operator ordering. See Q1229 for priority-based custom ordering.

defines module qa.operators.coalesce.customcmp

  defines record

    Measurement
      label as String: String()
      reading as Float: 0.0

      Measurement()
        ->
          label as String
          reading as Float
        this.label: label
        this.reading: reading

      operator <=> as pure
        -> other as Measurement
        <- rtn as Integer: reading <=> other.reading

      default operator

  defines function

    smallerReading() as pure
      ->
        left as Measurement
        right as Measurement
      <- rtn as Measurement: left <? right

  defines program

    CoalesceCustomCmpDemo()
      stdout <- Stdout()

      // === BOTH SET — compares by reading only ===

      sensorA <- Measurement("Station-North", 22.5)
      sensorB <- Measurement("Station-South", 18.3)
      smaller <- smallerReading(sensorA, sensorB)
      stdout.println(`Smaller reading: ${smaller}`)

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

      calibrated <- Measurement("Lab", 15.0)
      uncalibrated <- Measurement()
      usable <- smallerReading(calibrated, uncalibrated)
      stdout.println(`Usable reading: ${usable}`)

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

      offlineA <- Measurement()
      offlineB <- Measurement()
      noReading <- smallerReading(offlineA, offlineB)
      if noReading?
        stdout.println(`Reading: ${noReading}`)
      else
        stdout.println("No reading available")

      // === >? FOR LARGER READING ===

      larger <- sensorA >? sensorB
      stdout.println(`Larger reading: ${larger}`)

Java: Comparator.comparingDouble(Measurement::getReading). Python: min(a, b, key=lambda m: m.reading). Rust: impl Ord comparing self.reading. EK9: custom <=> on reading field, then left <? right.

<? coalescing comparison custom measurement minimum reading record

custom <=> for specific field
<? with custom ordering
measurement comparison


Question: Find the nearest Waypoint from two optional locations using <? on a class with distance.

How do I use <? coalescing on a class instead of a record?

Given two optional Waypoint objects with distance fields, pick the nearer one.

In Java I'd implement Comparable on a class to use min(). What does EK9 use?

Migrating from Python where I use min(wp1, wp2, key=lambda w: w.distance) — what is the EK9 pattern?

Answer:
Define a class with private fields, accessor methods, a custom <=> operator comparing distance, and 'default operator' last. Then use <? in a pure function.

  nearerWaypoint() as pure
    -> left as Waypoint, right as Waypoint
    <- rtn as Waypoint: left <? right

Classes are used instead of records when you need behaviour (methods). Waypoint has name() and distance() accessor methods because class fields are always private. The <? operator works identically on classes and records — it only requires <=> to be defined.

See Q1228 for <? on records.

defines module qa.operators.coalesce.classmethod

  defines class

    Waypoint
      waypointName <- String()
      waypointDistance <- Float()

      Waypoint()
        ->
          name as String
          distance as Float
        waypointName :=: name
        waypointDistance :=: distance

      name() as pure
        <- rtn as String: waypointName

      distance() as pure
        <- rtn as Float: waypointDistance

      operator <=> as pure
        -> other as Waypoint
        <- rtn as Integer: waypointDistance <=> other.waypointDistance

      default operator

  defines function

    nearerWaypoint() as pure
      ->
        left as Waypoint
        right as Waypoint
      <- rtn as Waypoint: left <? right

  defines program

    CoalesceClassMethodDemo()
      stdout <- Stdout()

      // === BOTH SET — compares by distance ===

      basecamp <- Waypoint("Basecamp", 2.5)
      summit <- Waypoint("Summit", 8.7)
      nearer <- nearerWaypoint(basecamp, summit)
      stdout.println(`Nearer waypoint: ${nearer}`)

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

      knownPoint <- Waypoint("Checkpoint", 5.0)
      unknownPoint <- Waypoint()
      available <- nearerWaypoint(knownPoint, unknownPoint)
      stdout.println(`Available waypoint: ${available}`)

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

      lostA <- Waypoint()
      lostB <- Waypoint()
      noWaypoint <- nearerWaypoint(lostA, lostB)
      if noWaypoint?
        stdout.println(`Waypoint: ${noWaypoint}`)
      else
        stdout.println("No waypoint available")

      // === >? FOR FARTHEST ===

      farthest <- basecamp >? summit
      stdout.println(`Farthest waypoint: ${farthest}`)

Java: class Waypoint implements Comparable<Waypoint> with compareTo(). Python: class with __lt__ and min(). Kotlin: class with compareTo(). EK9: class with <=> operator, then left <? right.

<? accessor class coalescing distance method minimum waypoint

<? coalescing on classes
class vs record for coalescing
accessor methods

Explanation: Class fields are always private. To expose state, define accessor methods. Public fields are only allowed on records. See ek9 -h E07290 for details.


Question: Apply a default Configuration record only if no configuration was provided.

How do I use :=? guarded assignment with a record type?

Given an optional Configuration record, apply defaults if it is unset.

In Java I'd check if config == null before setting defaults. What does EK9 offer?

Migrating from Kotlin where I use config ?: defaultConfig — what is the EK9 equivalent?

Answer:
The :=? guarded assignment operator works on ANY type, not just primitives. It assigns the value only if the target variable is currently unset.

  config <- Configuration()
  defaultConfig <- Configuration("localhost", 8080)
  config :=? defaultConfig

If config is unset, it receives the default. If config already has a value, the assignment is skipped. This is an ASSIGNMENT operator.

See Q1220 for :=? on strings.

defines module qa.operators.guardassign.record

  defines record

    Configuration
      host as String: String()
      port as Integer: 0

      Configuration()
        ->
          host as String
          port as Integer
        this.host: host
        this.port: port

      default operator

  defines function

    loadSavedConfig()
      <- rtn <- Configuration()
      //Simulates: no saved configuration found

    loadEnvironmentConfig()
      <- rtn <- Configuration("env-host", 9090)

  defines program

    GuardAssignRecordDemo()
      stdout <- Stdout()

      // === RECORD IS UNSET — :=? assigns the default ===

      config <- Configuration()
      defaultPort <- 8080
      defaultConfig <- Configuration("localhost", defaultPort)
      config :=? defaultConfig
      stdout.println(`Config: ${config}`)

      // === RECORD IS ALREADY SET — :=? is skipped ===

      existingConfig <- Configuration("production.example.com", 443)
      existingConfig :=? defaultConfig
      stdout.println(`Existing preserved: ${existingConfig}`)

      // === FALLBACK CHAIN — first set value wins ===

      appConfig <- Configuration()
      appConfig :=? loadSavedConfig()
      appConfig :=? loadEnvironmentConfig()
      fallbackPort <- 3000
      appConfig :=? Configuration("fallback-host", fallbackPort)
      stdout.println(`App config: ${appConfig}`)

Java: if (config == null) config = defaultConfig. Python: config = config or defaultConfig. Kotlin: config = config ?: defaultConfig. EK9: config :=? defaultConfig — one operator, works on any type.

:=? assignment configuration default guarded record unset user-defined

:=? on user-defined records
guarded assignment beyond primitives
configuration defaults

      if config == null
        config := defaultConfig

Explanation: EK9 has no null. Use :=? to conditionally assign when a variable is unset. This works on records, classes, and all types. See ek9 -h E01072 for details.


Question: Process a list of optional sensor readings and select the minimum using stream join with <?.

How do I reduce a stream of custom records to find the minimum?

Given sensor readings from multiple stations, find the coolest using a pipeline.

In Java I'd use stream().min(Comparator.comparing(SensorReading::getTemperature)). What does EK9 use?

Migrating from Python where I use min(readings, key=lambda r: r.temperature) — how does EK9 do this?

Answer:
Define a pure function using <? and pass it to 'join with' in a stream pipeline. The join operation reduces the stream to a single value.

  coolerOf() as pure
    -> left as SensorReading, right as SensorReading
    <- rtn as SensorReading: left <? right

  coolest <- cat readings | join with coolerOf | collect as SensorReading

The <? operator handles unset values gracefully within the stream. The custom <=> on temperature determines ordering.

See Q1139 for join basics. See Q1230 for custom <=> on records.

defines module qa.streams.coalesce.pipeline

  defines record

    SensorReading
      stationId as String: String()
      temperature as Float: 0.0

      SensorReading()
        ->
          stationId as String
          temperature as Float
        this.stationId: stationId
        this.temperature: temperature

      operator <=> as pure
        -> other as SensorReading
        <- rtn as Integer: temperature <=> other.temperature

      default operator

  defines function

    coolerOf() as pure
      ->
        left as SensorReading
        right as SensorReading
      <- rtn as SensorReading: left <? right

    warmerOf() as pure
      ->
        left as SensorReading
        right as SensorReading
      <- rtn as SensorReading: left >? right

  defines program

    CoalesceInPipelineDemo()
      stdout <- Stdout()

      // === BUILD A LIST OF SENSOR READINGS ===

      readings <- List() of SensorReading
      readings += SensorReading("North", 22.5)
      readings += SensorReading("South", 18.3)
      readings += SensorReading("East", 25.1)
      readings += SensorReading("West", 15.8)

      // === FIND COOLEST USING STREAM JOIN WITH <? ===

      coolestList <- cat readings | join with coolerOf | collect as List of SensorReading
      stdout.println(`Coolest: ${coolestList}`)

      // === FIND WARMEST USING STREAM JOIN WITH >? ===

      warmestList <- cat readings | join with warmerOf | collect as List of SensorReading
      stdout.println(`Warmest: ${warmestList}`)

      // === SORTED OUTPUT ===

      stdout.println("All readings sorted by temperature:")
      cat readings | sort > stdout

Java: stream().min(Comparator.comparingDouble(SensorReading::getTemperature)). Python: min(readings, key=lambda r: r.temperature). Rust: readings.iter().min_by_key(|r| r.temperature). EK9: cat readings | join with coolerOf | collect as SensorReading.

<? coalescing join minimum pipeline record reduce sensor stream

<? in stream pipelines
join with coalescing function
stream reduction on records

Explanation: EK9 List has no .stream() method; reduce with a stream pipeline 'cat list | join with fn'. See ek9 -h E50060 for details.


Question: Create me a generic Processor class of type T with an abstract process method, then extend it inline using a dynamic class with String as the type parameter.

Write a generic abstract class with one typed method and provide a dynamic class implementation.

Show me how to extend a generic abstract class inline with a concrete type using a dynamic class.

Implement a generic Processor of T that I can extend at the use site for a specific type.

Create a parameterised abstract class and instantiate it for String.

Answer:
Generic abstract classes in EK9 use 'of type T' to declare a type parameter. They can be extended INLINE in expression position using a dynamic class with the syntax '() extends ParentName of ConcreteType as class'. The dynamic class overrides the abstract method, with the type parameter T replaced by the concrete type.

GENERIC ABSTRACT CLASS
  Processor of type T as abstract
    process() as abstract
      -> arg0 as T
      <- rtn as String?
    override operator ? as pure
      <- rtn as Boolean: true

DYNAMIC CLASS EXTENSION
The dynamic class is created at the call site:
  instance <- () extends Processor of String as class
    override process()
      -> arg0 as String
      <- rtn as String: "Processed: " + arg0

USAGE
  result <- instance.process("Hello")
  stdout.println(result)

The '() extends Processor of String as class' form parameterises the parent generic with a concrete type AND creates an instance in one expression. This is more concise than declaring a named subclass when only one usage is needed.

See Q642 for generic constructor inference. See Q649 for generic function implementation. See Q194 for generic class basics.

defines module qa.genericsdeep.processorbasic

  defines class

    Processor of type T as abstract

      process() as abstract
        -> arg0 as T
        <- rtn as String?

      override operator ? as pure
        <- rtn as Boolean: true

  defines program

    ProcessorDemo()
      stdout <- Stdout()

      instance <- () extends Processor of String as class
        override process()
          -> arg0 as String
          <- rtn as String: "Processed: " + arg0

      result <- instance.process("Hello")
      stdout.println(result)

Java: anonymous inner class extending Processor<String> { @Override public String process(String arg) { ... } }. Kotlin: object : Processor<String>() { override fun process(arg: String) = ... }. Scala: new Processor[String] { override def process(arg: String) = ... }. EK9: '() extends Processor of String as class' is the dynamic class form for inline parameterised extension.

T
abstract
dynamic class
extends
generic
inline
of type
type parameter

generic class extension
dynamic class with generic
of type T

Explanation: An abstract generic with abstract methods must be declared 'as abstract'. Using 'as open' implies the methods have bodies that may be overridden, which leads to unresolved type issues when the dynamic class tries to extend it. See ek9 -h E50040 for details.


Question: Write me a generic Formatter of type T that captures a prefix variable from the enclosing scope and uses it inside the overridden format method.

Create a dynamic class extending a generic type that captures a local variable.

Show me how generic dynamic classes combine with variable capture in EK9.

Implement a parameterised Formatter that captures a prefix and formats Integer values.

Build a generic formatter dynamic class that uses an outer-scope variable.

Answer:
Dynamic classes that extend a generic type can ALSO capture variables from the enclosing scope. The captured variables are listed in the parentheses before 'extends', and become fields on the dynamic class. They can be used inside the overridden methods alongside the type-substituted parameters.

GENERIC ABSTRACT FORMATTER
  Formatter of type T as abstract
    format() as abstract
      -> arg0 as T
      <- rtn as String?
    override operator ? as pure
      <- rtn as Boolean: true

DYNAMIC CLASS WITH CAPTURE
  prefix <- "Item"
  formatter <- (prefix) extends Formatter of Integer as class
    override format()
      -> arg0 as Integer
      <- rtn as String: `${prefix}: ${$arg0}`
    default operator ?

The parentheses '(prefix)' declare which outer variables to capture. They become fields on the anonymous class and are visible inside the overridden methods. The generic type parameter T is replaced by Integer at the same time.

USAGE
  stdout.println(formatter.format(42))

This combines TWO features in one expression: generic type extension AND closure-style capture. The captured prefix coexists with the substituted Integer parameter.

See Q1234 for basic generic extension without capture. See Q601 for dynamic functions with capture. See Q197 for extending generics.

defines module qa.genericsdeep.formattercapture

  defines class

    Formatter of type T as abstract

      format() as abstract
        -> arg0 as T
        <- rtn as String?

      override operator ? as pure
        <- rtn as Boolean: true

  defines program

    FormatterCaptureDemo()
      stdout <- Stdout()
      prefix <- "Item"

      formatter <- (prefix) extends Formatter of Integer as class
        override format()
          -> arg0 as Integer
          <- rtn as String: `${prefix}: ${$arg0}`
        default operator ?

      stdout.println(formatter.format(42))

Java: anonymous inner class capturing effectively-final outer variables. Kotlin: object expression with closure over outer scope. Scala: anonymous subclass with closure. JavaScript: class extending a generic-like base that closes over outer variables. EK9: explicit '(capturedVar)' syntax makes capture intentional and visible.

anonymous
capture
closure
dynamic class
extends
generic
inline
of type

generic dynamic class with capture
variable capture
anonymous extension

() extends Formatter of Integer as class

Explanation: If you reference an outer-scope variable inside the dynamic class body but do not list it in the capture parentheses, the variable is not resolved (E50001) because it is never brought into the dynamic class's scope. List it in the capture parentheses, e.g. (prefix), to make it available. See ek9 -h E50001 for details.


Question: Create me a dispatcher method for a sealed Shape trait that has Circle, Square and Triangle handlers, demonstrating exhaustive runtime dispatch.

Write a dispatcher with handlers for every permitted type of a sealed trait.

Implement exhaustive dispatch over a sealed Shape hierarchy.

Show me how to use a dispatcher with 'allow only' to enforce exhaustive handling.

Build a ShapeProcessor that dispatches differently for Circle, Square and Triangle.

Answer:
A dispatcher method declared with 'as dispatcher' resolves at runtime to the most specific overload based on the actual parameter type. When the parameter type is a sealed trait declared with 'allow only', you can write one handler per permitted type and the compiler ensures exhaustive coverage.

SEALED TRAIT
  Shape allow only Circle, Square, Triangle
    name() as abstract
      <- rtn as String?

IMPLEMENTING CLASSES
  Circle with trait of Shape
    override name()
      <- rtn as String: "Circle"

  Square with trait of Shape
    override name()
      <- rtn as String: "Square"

  Triangle with trait of Shape
    override name()
      <- rtn as String: "Triangle"

DISPATCHER ENTRY AND HANDLERS
The entry takes the trait type; each handler takes a permitted concrete type:
  ShapeProcessor
    describe() as dispatcher
      -> shape as Shape
      <- rtn as String: "Unknown"

    describe()
      -> shape as Circle
      <- rtn as String: "Handled Circle"

    describe()
      -> shape as Square
      <- rtn as String: "Handled Square"

    describe()
      -> shape as Triangle
      <- rtn as String: "Handled Triangle"

USAGE
  processor <- ShapeProcessor()
  stdout.println(processor.describe(Circle()))
  stdout.println(processor.describe(Square()))
  stdout.println(processor.describe(Triangle()))

Each call selects the handler whose parameter most closely matches the runtime type. The 'allow only' clause means the compiler can verify ALL permitted types have a handler.

See Q618 for sealed exhaustive dispatch rules. See Q612 for purity matching. See Q60 for dispatcher fundamentals.

defines module qa.dispatcher.sealedshape

  defines trait

    Shape allow only Circle, Square, Triangle
      name() as abstract
        <- rtn as String?

  defines class

    Circle with trait of Shape
      override name()
        <- rtn as String: "Circle"

    Square with trait of Shape
      override name()
        <- rtn as String: "Square"

    Triangle with trait of Shape
      override name()
        <- rtn as String: "Triangle"

    ShapeProcessor

      describe() as dispatcher
        -> shape as Shape
        <- rtn as String: "Unknown"

      describe()
        -> shape as Circle
        <- rtn as String: "Handled Circle"

      describe()
        -> shape as Square
        <- rtn as String: "Handled Square"

      describe()
        -> shape as Triangle
        <- rtn as String: "Handled Triangle"

  defines program

    SealedShapeDispatchDemo()
      stdout <- Stdout()
      processor <- ShapeProcessor()

      stdout.println(processor.describe(Circle()))
      stdout.println(processor.describe(Square()))
      stdout.println(processor.describe(Triangle()))

Java: visitor pattern with accept(visitor) on each subclass. Scala: pattern matching on sealed trait. Kotlin: when expression on sealed class. Rust: match on enum variants. EK9: dispatcher method with one handler per permitted type — no boilerplate, type-checked, exhaustiveness verified.

allow only
dispatch
dispatcher
exhaustive
handler
runtime
sealed
trait

sealed trait dispatcher
exhaustive dispatch
allow only

describe() as pure dispatcher
        -> shape as Shape
      describe()
        -> shape as Circle

Explanation: The dispatcher entry and all handlers must agree on purity. If the entry is 'as pure dispatcher', every handler must also be 'as pure'. The compiler detects this as a method signature conflict. See ek9 -h E07120 for details.


Question: Write me a pure function that takes a sanitized String parameter and returns a different status string for safe input versus blocked malicious input.

Create a function with a sanitized String parameter and demonstrate it on SQL injection and XSS inputs.

Show me how the sanitized keyword works on a function parameter in EK9.

Implement an input-checking function that uses the sanitized modifier.

Build a processInput function that rejects malicious payloads.

Answer:
The 'sanitized' modifier on a parameter declares that the value is treated as untrusted external input and must pass the sanitization pipeline before the function body sees it. Inside the function, an unset value indicates the input was rejected and a set value indicates the input is safe to use.

FUNCTION WITH SANITIZED PARAMETER
  processInput()
    -> input as sanitized String
    <- result as String: "unset"
    if input?
      result: "safe"
    else
      result: "blocked"

The 'sanitized' keyword is part of the parameter declaration. The compiler enforces that the function only sees sanitized values — malicious inputs like SQL injection or XSS payloads result in an unset parameter, NOT a thrown exception.

DEMONSTRATING SAFE AND BLOCKED INPUTS
  // Safe input — passes through
  result1 <- processInput("Hello World")
  stdout.println("Safe: " + result1)        // "Safe: safe"

  // SQL injection — blocked
  result2 <- processInput("'; DROP TABLE users--")
  stdout.println("SQL: " + result2)         // "SQL: blocked"

  // XSS — blocked
  result3 <- processInput("<script>alert('xss')</script>")
  stdout.println("XSS: " + result3)         // "XSS: blocked"

KEY POINT
The function does not check for malicious patterns itself. The 'sanitized' modifier is the contract — the compiler and runtime ensure the parameter is either safe or unset. The function body simply checks 'input?' to know which case it is in.

See Q215 for sanitized parameters basics. See Q269 for input validation patterns. See Q272 for defense in depth.

defines module qa.security.sanitizedfunction

  defines function

    processInput()
      -> input as sanitized String
      <- result as String: "unset"
      if input?
        result: "safe"
      else
        result: "blocked"

  defines program

    SanitizedFunctionDemo()
      stdout <- Stdout()

      stdout.println("=== Sanitized Function Param Demo ===")

      safe <- "Hello World"
      result1 <- processInput(safe)
      stdout.println("Safe: " + result1)

      sqlInject <- "'; DROP TABLE users--"
      result2 <- processInput(sqlInject)
      stdout.println("SQL: " + result2)

      xss <- "<script>alert('xss')</script>"
      result3 <- processInput(xss)
      stdout.println("XSS: " + result3)

      safeName <- "John O'Brien"
      result4 <- processInput(safeName)
      stdout.println("Name: " + result4)

      stdout.println("=== Complete ===")

Java: manual validation with OWASP ESAPI or Bean Validation annotations. Python: html.escape and re.sub patterns or bleach library. Rust: newtype pattern with manual validation in the constructor. Go: explicit if-err checks after every input. EK9: 'sanitized' modifier delegates input validation to the runtime; the function only sees safe values.

SQL injection
XSS
function
input
parameter
sanitized
security
untrusted
validation

sanitized parameter
input sanitization
function security

Explanation: The 'sanitized' modifier comes BEFORE the type, not after. The correct form is 'input as sanitized String'. See ek9 -h E50001 for details.


Question: Implement me a strategy pattern for applying different discount calculations to a price, using an abstract function and two dynamic function strategies.

Create a Discounter strategy with two implementations and a function that applies the chosen strategy.

Show me how to swap discount algorithms at runtime using EK9 abstract functions.

Write a price calculator that takes a discount strategy as a parameter.

Build a strategy pattern example for ten-percent and twenty-percent discounts.

Answer:
EK9 implements the strategy pattern using an abstract function as the strategy interface and dynamic functions as concrete strategies. A separate function takes the value AND the strategy as parameters, calling the strategy through the function-call syntax.

ABSTRACT FUNCTION AS STRATEGY
  Discounter() as pure abstract
    -> price as Float
    <- rtn as Float?

DYNAMIC FUNCTION STRATEGIES
Each concrete strategy is a dynamic function with the signature of the abstract:
  tenPercent <- () is Discounter as pure function
    rtn: price * 0.9

  twentyPercent <- () is Discounter as pure function
    rtn: price * 0.8

The '() is Discounter as pure function' form creates an instance of the abstract function with a concrete body. The body uses 'rtn:' to assign the return variable.

PURE FUNCTION THAT TAKES A STRATEGY
The strategy is passed as a parameter typed by the abstract function:
  applyDiscount() as pure
    ->
      price as Float
      strategy as Discounter
    <- rtn as Float: strategy(price)

Calling 'strategy(price)' invokes whichever concrete strategy was passed in.

USAGE
  full <- 100.0
  cheaper <- applyDiscount(full, tenPercent)
  cheapest <- applyDiscount(full, twentyPercent)
  stdout.println(`Full: ${full}, 10% off: ${cheaper}, 20% off: ${cheapest}`)

KEY ADVANTAGES
No class hierarchy needed. The abstract function defines the contract; dynamic functions provide implementations; the consumer just calls the parameter as a function. This is more concise than the Java/C# strategy interface + class implementations approach.

See Q214 for strategy pattern basics. See Q57 for strategy without subclassing. See Q52 for dynamic functions.

defines module qa.patterns.discountstrategy

  defines function

    Discounter() as pure abstract
      -> price as Float
      <- rtn as Float?

    applyDiscount() as pure
      ->
        price as Float
        strategy as Discounter
      <- rtn as Float: strategy(price)

  defines program

    DiscountStrategyDemo()
      stdout <- Stdout()

      tenPercent <- () is Discounter as pure function
        rtn: price * 0.9

      twentyPercent <- () is Discounter as pure function
        rtn: price * 0.8

      full <- 100.0
      cheaper <- applyDiscount(full, tenPercent)
      cheapest <- applyDiscount(full, twentyPercent)

      stdout.println(`Full: ${full}, 10% off: ${cheaper}, 20% off: ${cheapest}`)

Java: Strategy interface with implementing classes (DiscountStrategy interface, TenPercentDiscount implements). Python: pass functions directly (first-class). Kotlin: function types ((Double) -> Double) or interface implementations. Rust: trait objects (Box<dyn Discounter>) or closures. Go: function types. EK9: abstract function as the contract and dynamic functions as implementations — more concise than interface+class but with explicit type contract.

abstract function
behaviour parameterisation
callback
discount
dynamic function
pattern
strategy
swap algorithm

strategy pattern
abstract function as interface
dynamic function strategy

Explanation: An abstract function with no body must be declared 'as abstract' (or 'as pure abstract'). Using 'as open' is for functions WITH a body that can be overridden. See ek9 -h E07110 for details.


Control Flow Without break/continue/return

Question: Convert me a Java for-loop with break (find the first item over a threshold) into the equivalent EK9 stream pipeline.

Migrate a Java for/break loop to EK9 without using break.

Show me how to replace 'for + break on first match' with an EK9 stream.

Rewrite a Java early-exit loop in idiomatic EK9.

How do I express 'find first over N' without a loop in EK9?

Answer:
EK9 has no break statement, so loops with early exit on first match are expressed as a stream pipeline using filter + head. The pipeline is more declarative AND eliminates the loop variable that Java needed.

THE JAVA PATTERN
  Integer firstBig = null;
  for (Integer n : numbers) {
    if (n > 50) {
      firstBig = n;
      break;
    }
  }
  if (firstBig != null) System.out.println(firstBig);

Three problems: a mutable accumulator, a break statement, and a null check. EK9 eliminates all three.

THE EK9 STREAM EQUIVALENT
Use filter to keep only matching items, then head to take the first one, then collect into a List:
  isBig() as pure
    -> n as Integer
    <- big as Boolean: n > 50

  firstBig <- cat numbers | filter by isBig | head 1 | collect as List of Integer

The pipeline reads top-to-bottom: 'cat' streams the list, 'filter' keeps matches, 'head 1' takes the first, 'collect' produces a final List. There is no loop variable, no mutable accumulator, no break.

USAGE
  numbers <- [10, 25, 60, 75, 90]
  firstBig <- cat numbers | filter by isBig | head 1 | collect as List of Integer
  if firstBig?
    stdout.println(`First > 50: ${firstBig}`)

WHY EK9 PREFERS THIS
1. No break — loop control is implicit in the pipeline.
2. No mutable accumulator — the result is an expression.
3. The filter predicate is reusable and unit-testable on its own.
4. Adding 'sort' or 'map' is one more pipe stage, not a refactor.

See Q284 for find-first via guarded assignment. See Q288 for migrating break loops. See Q142 for stream pipeline basics.

defines module qa.without.streamfindfirst

  defines constant

    BIG_THRESHOLD <- 50

  defines function

    isBig() as pure
      -> n as Integer
      <- big as Boolean: n > BIG_THRESHOLD

  defines program

    MigrateBreakDemo()
      stdout <- Stdout()

      numbers <- [10, 25, 60, 75, 90]

      firstBig <- cat numbers | filter by isBig | head 1 | collect as List of Integer

      if firstBig?
        stdout.println(`First > ${BIG_THRESHOLD}: ${firstBig}`)

Java: for + break, or stream().filter().findFirst().orElse(null). Python: next((x for x in items if predicate(x)), None). Kotlin: items.firstOrNull { predicate(it) }. Rust: items.iter().find(|x| predicate(x)). Go: explicit for loop with break. EK9: 'cat items | filter by predicate | head 1 | collect as List of T' — explicit pipeline without break and without nullable return.

Java to EK9
break
early exit
filter
find first
head
migration
stream
without break

replace break with stream
filter head pipeline
find first match

for n in numbers
        if n > 50
          firstBig: n
          break

Explanation: EK9 has no 'break' statement. Use a stream pipeline with 'filter | head 1' to take only the first match, eliminating the need for early loop exit. See ek9 -h E01070 for details.


Question: Write me a pure function categorise that takes an Integer score and returns a String category ('high', 'medium' or 'low'), proving the return variable is initialised on every branch.

Create a function whose return value is provably set on every if/else branch.

Show me how to satisfy EK9 data flow analysis with branch initialisation.

Implement a categoriser that the compiler can prove always returns a value.

Write a function with a default-initialised return variable for safety.

Answer:
EK9 tracks the initialisation state of every variable through all control flow paths. A return variable can be made provably initialised in TWO ways: provide a default value at declaration time, or assign in EVERY branch of every conditional.

PATTERN 1: DEFAULT VALUE AT DECLARATION (RECOMMENDED)
The return variable starts with a sensible default. Branches override it as needed:
  categorise() as pure
    -> score as Integer
    <- rtn as String: "low"
    if score >= 80
      rtn: "high"
    else if score >= 50
      rtn: "medium"

The compiler accepts this because 'rtn' is initialised before any branch runs. The if/else only modifies an already-set value.

PATTERN 2: EXPLICIT ASSIGNMENT ON EVERY BRANCH
Declare the return variable without a default but assign in every branch:
  categorise() as pure
    -> score as Integer
    <- rtn as String?
    if score >= 80
      rtn: "high"
    else if score >= 50
      rtn: "medium"
    else
      rtn: "low"

The final 'else' is required — without it, a score below 50 would leave 'rtn' uninitialised and the compiler raises E08020.

WHY DEFAULT IS PREFERRED
Pattern 1 is more robust: adding a new branch (e.g. 'if score >= 95 then "excellent"') does not risk leaving rtn uninitialised because the default still applies to any path that does not override it. Pattern 2 requires updating the else clause whenever cases change.

USAGE
  stdout.println(categorise(95))   // "high"
  stdout.println(categorise(70))   // "medium"
  stdout.println(categorise(30))   // "low"

See Q633 for branch initialisation rules. See Q632 for definition order. See Q634 for guard-based safe access.

defines module qa.dataflow.categorise

  defines constant

    HIGH_THRESHOLD <- 80

    MEDIUM_THRESHOLD <- 50

  defines function

    categorise() as pure
      -> score as Integer
      <- rtn as String: "low"
      if score >= HIGH_THRESHOLD
        rtn: "high"
      else if score >= MEDIUM_THRESHOLD
        rtn: "medium"

  defines program

    CategoriseDemo()
      stdout <- Stdout()

      stdout.println(`Score 95: ${categorise(95)}`)
      stdout.println(`Score 70: ${categorise(70)}`)
      stdout.println(`Score 30: ${categorise(30)}`)

Java: definite assignment analysis catches some of these but allows null. Rust: must assign on every path or use Option<T>. Go: zero-value initialisation always works but may hide bugs. Python: NameError at runtime. EK9: compile-time guarantee — either declare with default or assign on every branch, no third option.

E08020
branch
data flow
default value
if else
initialisation
return variable
uninitialised

return variable initialisation
branch safe assignment
default declaration

<- rtn as String?
      if score >= 80
        rtn: "high"
      else if score >= 50
        rtn: "medium"

Explanation: Without a default value AND without a final else, rtn is uninitialised when score < 50. Either provide a default at declaration or add an else branch that assigns rtn. See ek9 -h E08020 for details.