What does 'x := f()' do when f() returns an unset value, and how do I keep the old value instead?
← Control Flow · Ref: Q1981
':=' ALWAYS assigns the right side to the existing variable - an unset result included. An assignment never yields a value, so to know whether the new value is set, check it afterwards:
latest := fetchReading() // latest = 42 gotOne <- latest? // true latest := fetchNothing() // latest is now UNSET gotAnother <- latest? // false
latest always reflects the most recent call. In a control-flow head the same check gates the body: 'if latest := f() then latest?'.
TO KEEP THE OLD VALUE WHEN THE NEW ONE IS UNSET, USE ?:
kept := fetchNothing() ?: kept
'?:' gives its left side when it is SET, otherwise its right - so kept stays 42.
?? DOES NOT DO THIS
passed := fetchNothing() ?? passed
'??' only replaces an ABSENT value (no object at all). An unset Integer is present, so '??' passes it straight through and passed becomes unset.
TO FILL A VARIABLE ONLY WHILE IT IS UNSET, USE :=?
fallback :=? fetchReading()
':=?' looks at the target, not the new value: it assigns only when fallback is unset, and does not even call fetchReading() otherwise.
CHOOSING
always take the new value, know if it is set x := f() then ok <- x? take the new value only if it is set x := f() ?: x set x only if it has no value yet x :=? f()
See Q1980 for the pre-flow operators in a control-flow head, Q1982 for the combinations the compiler rejects, Q1059 for ?? versus ?:, Q79 for :=?.
Example
defines module qa.controlflow.guardfamily.expression defines function fetchReading() <- rtn as Integer: 42 fetchNothing() <- rtn as Integer: Integer() defines program GuardAsAnExpression() stdout <- Stdout() //':=' ALWAYS assigns; 'latest?' afterwards says whether the new value is set latest <- fetchNothing() stdout.println(`before: latest? ${latest?}`) latest := fetchReading() gotOne <- latest? stdout.println(`set result: gotOne=${gotOne} latest=${latest}`) latest := fetchNothing() gotAnother <- latest? stdout.println(`unset result: gotAnother=${gotAnother} latest? ${latest?}`) //To KEEP the old value when the new one is unset, use ?: - left if set, otherwise right kept <- fetchReading() kept := fetchNothing() ?: kept stdout.println(`?: keeps the old value: kept=${kept}`) //?? is not the same: it only replaces an ABSENT value, and an unset one is present passed <- fetchReading() passed := fetchNothing() ?? passed stdout.println(`?? passes the unset value through: passed? ${passed?}`) //When the variable may be unset and should only be filled, use :=? fallback <- fetchNothing() fallback :=? fetchReading() stdout.println(`:=? fills an unset variable: fallback=${fallback}`)
Common mistakes
E08095 — Starting fallback from a literal makes it always set, so the ':=?' below can never assign. ':=?' is for a variable that may be unset; to replace a set value use ':=', or '?:' to keep it when the new one is unset. See ek9 -h E08095 for details.
Incorrect:
fallback <- 0
Correct:
fallback <- fetchNothing()
Other ways to ask this
- Does ':=' assign an unset result?
- How do I assign and then know whether the new value is set?
- How do I assign a new value only when it is set and otherwise keep the old one?
- Why does '??' not keep my old value when the new one is unset?
- What is the difference between :=, ?: and :=? for updating a variable?
Coming from another language?
Kotlin: 'x = f() ?: x' is the keep-old idiom and maps to EK9's '?:'; 'f()?.let { x = it }' (assign only when non-null) has no single EK9 operator by design - write 'x := f() ?: x'. C#/JavaScript/Swift: '??' is the keep-old idiom there, but EK9's '??' only tests for an ABSENT value, so an unset result passes through it. EK9 has no assignment that also reports set-ness: assign with ':=', then test with '?'.
Keywords: ??, elvis, Boolean, unset, default, :=, always assigns, keep old value, ?:, coalescing, :=?, assign then check