How do I accumulate partial results without break?

← Control Flow Without break/continue/return · Ref: Q286

In Java you write: result = null; for (...) { if (match) { result = x; break; } }. In EK9, the :=? guarded assignment operator is purpose-built for first-assignment-wins.

THE PROBLEM

You want to find a value from the first matching source in priority order. Other languages use a loop with break-on-found. The break is the mechanism for stopping after the first match.

PATTERN 1: PRIORITY CHAIN WITH :=?

Try each source in priority order. The first one that sets the value wins:

  resolveConfig()
    -> envSetting as String, fileSetting as String, hardcoded as String
    <- setting as String: String()
    setting :=? envSetting
    setting :=? fileSetting
    setting :=? hardcoded

The :=? operator only assigns when the target is unset. Once setting gets a value from envSetting, the subsequent assignments are no-ops. No loop, no break.

PATTERN 2: STREAM COLLECT FOR BATCH

When accumulating ALL matching items (not first-wins), use stream collect:

  matches <- cat items | filter by isValid | collect as List of String

No break needed because you want everything that matches.

PATTERN 3: SWITCH WITH DECLARED RETURN

When dispatching on a single value, switch with a declared return replaces break-based dispatch:

  classify() as pure
    -> score as Integer
    <- label as String: "average"
    switch score
      case >= 90
        label: "excellent"
      case >= 70
        label: "good"
      case < 40
        label: "poor"
      default
        label: "average"

KEY INSIGHT

The :=? operator IS the break-on-found replacement. It was designed specifically for the first-assignment-wins pattern. No loops needed for priority lookup. No break needed for accumulation.

See Q50 for guarded assignment. See Q29 for :=? semantics. See Q79 for guarded assignment details. See Q122 for collect.

Example

defines module qa.without.accumulate

  defines function

    // Pattern 1: Priority chain
    resolveConfig()
      ->
        envSetting as String
        fileSetting as String
        hardcoded as String
      <- setting as String: String()
      setting :=? envSetting
      setting :=? fileSetting
      setting :=? hardcoded

    isValid() as pure
      -> item as String
      <- valid as Boolean?
      minValidLength <- 3
      valid: length item > minValidLength

    // Pattern 3: Switch with declared return
    classify() as pure
      -> score as Integer
      <- label as String: "average"
      switch score
        case >= 90
          label: "excellent"
        case >= 70
          label: "good"
        case < 40
          label: "poor"
        default
          label: "average"

  defines program

    AccumulateDemo()
      stdout <- Stdout()

      // === PATTERN 1: PRIORITY CHAIN ===

      defaultSetting <- "hardcoded"

      // Environment wins (first non-empty source)
      envResult <- resolveConfig("from-env", "from-file", defaultSetting)
      stdout.println(`Priority (env set): ${envResult}`)

      // File wins when env is unset
      fileResult <- resolveConfig(String(), "from-file", defaultSetting)
      stdout.println(`Priority (env unset): ${fileResult}`)

      // Hardcoded wins when both are unset
      defaultResult <- resolveConfig(String(), String(), defaultSetting)
      stdout.println(`Priority (both unset): ${defaultResult}`)

      // === PATTERN 2: STREAM COLLECT ===

      items <- ["hi", "apple", "banana", "ok", "cherry"]
      allValid <- cat items | filter by isValid | collect as List of String
      stdout.println(`All valid items: ${allValid}`)

      // === PATTERN 3: SWITCH DISPATCH ===

      scores <- [95, 72, 55, 38]
      for score in scores
        stdout.println(`Score ${score}: ${classify(score)}`)

      // === FIRST-WINS IN A LOOP ===

      names <- ["", "", "Alice", "Bob"]
      chosen <- String()
      for name in names
        if length name > 0
          chosen :=? name
      stdout.println(`First non-empty name: ${chosen}`)

Common mistakes

E50001 — Renaming the variable means later references to 'envResult' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details.

Incorrect:

envResultXYZ <- resolveConfig("from-env", "from-file", defaultSetting)

Correct:

envResult <- resolveConfig("from-env", "from-file", defaultSetting)
Other ways to ask this
  • How do I build a result from the first matching condition without break in EK9?
  • How does guarded assignment replace break-on-found in EK9?
  • How do I implement first-wins priority lookup in EK9?

Coming from another language?

Java: result = null; for (...) { if (match) { result = x; break; } } or Optional.orElse chain. Python: result = None; for ...: if match: result = x; break, or next() with generator. Rust: iter().find() or match with first arm. Go: for loop with break on found. Kotlin: firstOrNull() or generateSequence. JavaScript: find() or for loop with break. EK9: :=? guarded assignment for priority chain, stream collect for batch, switch for dispatch.

Keywords: error, lookup, null-safe, guarded, first, no-return, wins, build, collect, migrate, assignment, alternative, safe, result, isset, partial, config, accumulate, no-break, priority, ok