How do I run a block only when an EXISTING variable gets a set value?

← Control Flow · Ref: Q1962

Assign it with ':=' and check the new value in the condition: 'if reading := fetchReading() then reading?'. ':=' always assigns - an unset value included, overwriting what reading held - and checks nothing, so the check is written out after 'then'. The body runs only when reading is now set. (EK9 has no '?=' guard; this explicit form replaced it.)

  if reading := fetchReading() then reading?
    // runs only when reading is now set

A ':=' head with no condition is E07340: an assignment pre-flow checks nothing, so it needs 'then' (or 'with') and a condition.

WITH A FURTHER CONDITION

Put the set-check first and PARENTHESISE the rest: 'then sample? and (taken < wanted)'. 'and', 'or' and 'xor' share one precedence level, so 'sample? and a or b' would read as '(sample? and a) or b' and could enter with sample unset.

IN A LOOP

The pre-flow runs ONCE, before the loop; the condition (set-check included) is tested on every pass:

  while sample := firstSample() then sample? and (taken < wanted)

SWITCH, FOR, TRY AND DO

Their heads have no condition to carry the check, so assign first and wrap the construct: 'reading := fetchReading()' then 'if reading?' around the switch. If the variable is only used inside, a declaration guard does it in one head: 'switch r <- fetchReading()'.

See Q1960 for re-pointing inside a safe access block (E08040), Q1963 for why a ':=' head is not a guard and Q74 for guard variables in if.

Example

defines module qa.flow.guard.declarationoverguard

  defines function

    fetchReading()
      <- rtn as Integer: 21

    fetchNoReading()
      <- rtn as Integer: Integer()

    firstSample()
      <- rtn as Integer: 10

  defines program

    HeadGuardDemo()
      stdout <- Stdout()

      reading <- 0
      stdout.println(`before: reading=${reading}`)

      if reading := fetchReading() then reading?
        stdout.println(`guarded body: reading=${reading}`)
      stdout.println(`after a set value: reading set=${reading?}`)

      bodyRan <- false
      if reading := fetchNoReading() then reading?
        bodyRan: true
      stdout.println(`after an unset value: body ran=${bodyRan} reading set=${reading?}`)

      sample <- 0
      taken <- 0
      wanted <- 2
      while sample := firstSample() then sample? and (taken < wanted)
        stdout.println(`sample ${sample} taken ${taken}`)
        taken++

      stdout.println("Done")

Common mistakes

E07340 — A ':=' head assigns and checks nothing, so on its own it decides nothing; give it a condition - the set-check of the new value. See ek9 -h E07340 for details.

Incorrect:

      if reading := fetchReading()

Correct:

      if reading := fetchReading() then reading?
Other ways to ask this
  • Why does 'if reading := fetchReading()' need a 'then'?
  • How do I re-point a variable in an if head and check the new value?
  • How do I guard a block on an existing variable getting a value?
  • Why does a while loop re-check the variable on every pass?
  • What replaced the ?= guard in EK9?

Coming from another language?

Swift: 'if let v = f()' binds and tests in one step, but only a NEW name. Kotlin: 'x = f(); if (x != null)'. EK9: 'if x := f() then x?' re-points the existing variable and tests it in one head; for a new name, 'if v <- f()' declares and tests.

Keywords: then, :=, re-point, if head, existing variable, guard, pre-flow, set check, E07340