How do := and :=? work differently as guard operators in EK9 control flow?
← Control Flow · Ref: Q1025
The key difference is WHAT gets checked and WHEN the expression evaluates.
:= (BLIND ASSIGNMENT)
Always evaluates the expression. Always assigns. No isSet checks.
if count := getCount() with count > 0 process(count)
getCount() is called unconditionally. count is assigned unconditionally. The 'with' condition is the only gate.
:=? (ASSIGNMENT IF UNSET)
First checks if the target variable is already SET. Only evaluates the expression if the target is UNSET, and then assigns the result. The body runs only if the target is SET afterwards - so a target that already held a value enters the body with that value, and an unset result skips it.
if cache :=? expensiveCompute() with cache > threshold useCache(cache)
If cache already has a value, expensiveCompute() is NEVER called. This is lazy evaluation.
WHEN TO USE :=
- Fetching fresh data every time (sensor readings, API polls) - The value changes between calls and you want the latest - Combined with 'with' condition for validation
WHEN TO USE :=?
- Caching: avoid re-computing if already have a result - Configuration defaults: try sources in priority order - First-match: assign from first source that provides a value - Loop with lazy init: only initialize on first iteration
See Q1023 for all three operators compared. See Q79 for :=? standalone. See Q1024 for re-point-and-check in control flow.
Example
defines module qa.controlflow.assign.vs.assignifunset defines function fetchFreshData() <- rtn as Integer? rtn: 10 expensiveCompute() <- rtn as Integer? rtn: 99 defines program AssignVsAssignIfUnsetDemo() stdout <- Stdout() minCounter <- 5 threshold <- 50 // === := BLIND ASSIGNMENT in IF === // Always evaluates, always assigns. counter <- Integer() if counter := fetchFreshData() with counter > minCounter stdout.println(":= fetched and condition met: " + $counter) // === :=? ASSIGNMENT IF UNSET in IF === // cache starts unset -> expensiveCompute() IS called cache <- Integer() if cache :=? expensiveCompute() with cache > threshold stdout.println(":=? computed (was unset): " + $cache) // cache is now set to 99 -> expensiveCompute() is NOT called again if cache :=? expensiveCompute() with cache > threshold stdout.println(":=? skipped compute (already set): " + $cache) // === := ALWAYS RE-FETCHES (contrast with :=?) === // counter already has value 10, but := overwrites it if counter := fetchFreshData() with counter > minCounter stdout.println(":= re-fetched (always evaluates): " + $counter)
Common mistakes
E01072 — 'return' does not exist in EK9 — declare the return value with '<-' and assign it with 'rtn:'. See ek9 -h E01072 for details.
Incorrect:
return 10
Correct:
rtn: 10
Other ways to ask this
- When should I use := vs :=? in a while loop or if statement?
- What is the difference between blind assignment and assignment-if-unset in guards?
- How does :=? avoid re-evaluation in EK9 control flow?
- Show me := vs :=? in if and while in EK9
Coming from another language?
Java: ':=' is like simple assignment 'x = expr;'. ':=?' requires 'if (x == null || !x.isSet()) { var temp = expr(); if (temp != null && temp.isSet()) x = temp; }' — 4 lines vs 1. Go: No ':=?' equivalent. Must write 'if x == nil { x = compute() }'. Python: No direct equivalent. 'x = x or compute()' breaks for falsy values.
Keywords: lazy, if, assign, conditional, operator, blind, evaluation, control, default, compare, unset, cache, guard, flow, while