Walk me through using :=? on an Integer retry counter so that the counter only gets initialised on the first call and is preserved on subsequent calls.
← Operators and Expressions · Ref: Q1264
The :=? assign-if-unset operator works on any type, including Integer. It assigns the right-hand value ONLY when the target is currently unset. This is exactly what you want for 'initialise once, never overwrite' patterns like retry counters, lazy caches, and configuration defaults.
WORKED EXAMPLE: RETRY COUNTER
loadRetryCount()
<- rtn as Integer: Integer() // nothing stored yet: unset
retryCount as Integer: loadRetryCount()
// First call: assigns 0 because retryCount was unset retryCount :=? 0 stdout.println(`first :=? 0 -> ${retryCount}`)
// Second call: SKIPPED because retryCount is now set to 0 retryCount :=? 99 stdout.println(`second :=? 99 -> ${retryCount}`)
The second :=? does NOT change the value, even though 99 is different from 0. The counter is loaded rather than declared as Integer(): straight after 'retryCount as Integer: Integer()' the compiler can prove the second :=? never applies, and rejects it as dead code (E08095). The operator only checks 'is the target unset?', not 'is the value different?'.
INCREMENTING ON TOP OF GUARDED INIT
A common pattern is to guard the initial value, then mutate freely afterwards:
attempts as Integer: loadRetryCount() // may come back unset attempts :=? 0 // start from 0 only if it did attempts++ // 1 attempts++ // 2 attempts++ // 3 stdout.println(`attempts: ${attempts}`)
The :=? guarantees the counter starts from 0 even when nothing was stored. After that, ++ takes over. Where the compiler can SEE the value is unset - straight after 'attempts as Integer: Integer()' - ':=?' always applies, so it is a plain assignment and the compiler asks for 'attempts := 0', or better 'attempts as Integer: 0' (E08097).
WHY THIS IS SAFER THAN PLAIN :=
Using := blindly would overwrite the existing count and lose progress:
retryCount: 0 // Always overwrites — bad in restart-safe code retryCount :=? 0 // Only sets if unset — safe to call multiple times
For any value that should be 'set once and stay', :=? is the right operator.
See Q1263 for String assign-if-unset. See Q1265 for Date. See Q1266 for custom record. See Q1226 for the contrast between :=? and <?.
Example
#!ek9 defines module qa.operators.guardedassigninteger defines function loadRetryCount() <- rtn as Integer: Integer() defines program GuardedAssignIntegerDemo() stdout <- Stdout() retryCount as Integer: loadRetryCount() retryCount :=? 0 stdout.println(`first :=? 0 -> ${retryCount}`) retryCount :=? 99 stdout.println(`second :=? 99 -> ${retryCount}`) attempts as Integer: loadRetryCount() attempts :=? 0 attempts++ attempts++ attempts++ stdout.println(`attempts after ++: ${attempts}`)
Common mistakes
E50060 — Integer does not have a guardedSet method. The :=? operator is the correct way to perform assign-if-unset — it assigns only when the target is unset. See ek9 -h E50060 for details.
Incorrect:
retryCount.guardedSet(0)
Correct:
retryCount :=? 0
E08097 — Integer() makes an unset Integer, so the ':=?' after it always applies - a plain assignment written as a conditional one. Declare it with the value ('attempts as Integer: 0'). See ek9 -h E08097 for details.
Incorrect:
attempts as Integer: Integer()
Correct:
attempts as Integer: loadRetryCount()
Other ways to ask this
- How do I initialise an Integer counter only once using :=?
- Show me a worked example of assign-if-unset for a retry counter.
- Initialise an Integer to a default only when it has not been set.
- How does :=? behave on Integer values that may already be set?
Coming from another language?
Java: if (count == null) count = 0; — needs Integer wrapper, not int. Kotlin: count = count ?: 0. Rust: let count = count.unwrap_or(0); — but this rebinds, not modifies. Python: count = count if count is not None else 0. EK9: count :=? 0 — single operator, in-place, idempotent.
Keywords: :=?, Integer, assign-if-unset, default value, initialise once, lazy init, retry counter