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