Chapter 10: guards, reshaped

Chapter 10 is about guards: a value declared and checked in one breath, so code only runs once something is known to be set. Rather than type the guard directly, this lesson writes the plainer, two-statement form first - a declaration, then a separate check right after it, chapter 10's own "declare-then-check" pattern - and uses Lift into Guard to fold the two into one guarded head, the same tool a right-click offers wherever the caret is on a liftable control. To play it in the IDE: Help > Lessons > Writing code…, or ek9 -ide -lesson book-guards.

The EK9 IDE as the lesson opens

What you'll build

The lesson builds this program from a file holding only its module line - the finished file, as the book prints it.

#!ek9
defines module guards.forms

  defines function

    //Returns an unset String: there is no name to give.
    lookupName() as pure
      <- rtn as String: String()

    //Returns a set String.
    storedName() as pure
      <- rtn as String: "Ada"

    messagePrefix() as pure
      <- rtn as String: "Item"

  defines program

    //The guard: declare a value and run the block only if it is set.
    //lookupName() gives nothing, so the first block is skipped.
    Greet()
      stdout <- Stdout()
      if name <- lookupName()
        stdout.println(`Hello, ${name}`)
      if name <- storedName()
        stdout.println(`Hello, ${name}`)

    //A guard with an extra condition after 'then'.
    GreetLongNames()
      stdout <- Stdout()
      minLen <- 2
      if name <- storedName() then length name > minLen
        stdout.println(`Hello, ${name}`)

    //The same guard in a 'while' loop: the iterator is fetched once, hasNext() checked each time.
    Echo()
      stdout <- Stdout()
      lines <- ["red", "amber", "green"]
      while iter <- lines.iterator() then iter.hasNext()
        line <- iter.next()
        stdout.println(line)

    //The same guard as a one-off setup step before a 'for' loop.
    Numbered()
      stdout <- Stdout()
      for prefix <- messagePrefix() then i in 1 ... 3
        stdout.println(`${prefix} ${i}`)

//EOF

Step 1: lookupName

Choose Code > New Construct > Function…

The EK9 IDE once this is done: Choose Code > New Construct > Function…

lookupName is the function's name - no parameters. Set Returns to String and tick pure. Choose Preview.

The EK9 IDE just before choosing Preview: lookupName is the function's name - no parameters

The preview shows lookupName about to be written, pure, returning String. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows lookupName about to be written, pure, returning String

Select the lookupName() as pure line, and above it type the comment explaining why it returns nothing. Saves when typed.

The EK9 IDE once this is done: Select the lookupName() as pure line, and above it type the comment explaining why it returns nothing

Select the stub, from <- rtn as String: String() down to its //AI line, and type the unset String it already returns - the same text, its //AI marker gone. Saves when typed.

The EK9 IDE once this is done: Select the stub, from <- rtn as String

Step 2: storedName

Choose Code > New Construct > Function…

The EK9 IDE once this is done: Choose Code > New Construct > Function…

storedName is the function's name - no parameters. Set Returns to String, tick pure, and choose Preview.

The EK9 IDE just before choosing Preview: storedName is the function's name - no parameters

The preview shows storedName about to be written, pure, returning String; then the IDE formats the file it writes - a blank line already sets it apart from lookupName. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows storedName about to be written, pure, returning String; then the IDE formats the file it writes - a blank line already sets it apart from lookupName

Select the storedName() as pure line, and above it type the comment explaining what it returns - the blank line above it is already there. Saves when typed.

The EK9 IDE once this is done: Select the storedName() as pure line, and above it type the comment explaining what it returns - the blank line above it is already there

Select the stub, from <- rtn as String: String() down to its //AI line, and type the set String it really returns, "Ada". Saves when typed.

The EK9 IDE once this is done: Select the stub, from <- rtn as String

Step 3: messagePrefix

Choose Code > New Construct > Function…

The EK9 IDE once this is done: Choose Code > New Construct > Function…

messagePrefix is the function's name - no parameters. Set Returns to String, tick pure, and choose Preview.

The EK9 IDE just before choosing Preview: messagePrefix is the function's name - no parameters

The preview shows messagePrefix about to be written, pure. messagePrefix needs no comment of its own - the blank line a tool leaves before every member after the first is all that sets it apart. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows messagePrefix about to be written, pure

Select the stub, from <- rtn as String: String() down to its //AI line, and type the set String it really returns, "Item". Saves when typed.

The EK9 IDE once this is done: Select the stub, from <- rtn as String

Step 4: Greet — a guard, twice

Choose Code > New Construct > Program…

The EK9 IDE once this is done: Choose Code > New Construct > Program…

Greet is the program's name - no application to bind, no parameters, nothing injected. Type Greet and choose Preview.

The EK9 IDE just before choosing Preview: Greet is the program's name - no application to bind, no parameters, nothing injected

The preview shows Greet about to be written. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows Greet about to be written

Select the Greet() line, and above it type the two-line comment explaining what a guard does and why the first block below is skipped. Saves when typed.

The EK9 IDE once this is done: Select the Greet() line, and above it type the two-line comment explaining what a guard does and why the first block below is skipped

Select the stub, from ready <- true down to its //AI line, and type the plain, pre-lift form: a Stdout, name declared from lookupName(), then a SEPARATE check, if name?, before the greeting. Saves when typed.

The EK9 IDE once this is done: Select the stub, from ready <- true down to its //AI line, and type the plain, pre-lift form

Put the caret on if name? - the first one - and right-click, or choose Refactor > Refactor This…, and choose Lift into Guard…

The EK9 IDE once this is done: Put the caret on if name? - the first one - and right-click, or choose Refactor > Refactor This…, and choose Lift into Guard…

The preview shows name <- lookupName() and if name? folded into if name <- lookupName() - the separate check gone. Choose Apply.

Select the greeting line already there, and below it type storedName's own plain, pre-lift form - a second, independent name, then a separate if name? check - now that lookupName's own guard has already taken name's first life. Saves when typed.

The EK9 IDE once this is done: Select the greeting line already there, and below it type storedName's own plain, pre-lift form - a second, independent name, then a separate if name? check - now that lookupName's own guard has already taken name's first life

Put the caret on if name? - now the only one left - and right-click, or choose Refactor > Refactor This…, and choose Lift into Guard…

The EK9 IDE once this is done: Put the caret on if name? - now the only one left - and right-click, or choose Refactor > Refactor This…, and choose Lift into Guard…

The preview shows name <- storedName() and if name? folded the same way: if name <- storedName(). Choose Apply.

Step 5: GreetLongNames — a guard with a condition

Choose Code > New Construct > Program…

The EK9 IDE once this is done: Choose Code > New Construct > Program…

GreetLongNames is the program's name. Type it and choose Preview.

The EK9 IDE just before choosing Preview: GreetLongNames is the program's name

The preview shows GreetLongNames about to be written. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows GreetLongNames about to be written

Select GreetLongNames() and above it type the comment - the blank line above it is already there. Saves when typed.

The EK9 IDE once this is done: Select GreetLongNames() and above it type the comment - the blank line above it is already there

Select the stub, from ready <- true down to its //AI line, and type the plain, pre-lift form: name declared from storedName(), then if name? AND an extra condition on the name's length. Saves when typed.

The EK9 IDE once this is done: Select the stub, from ready <- true down to its //AI line, and type the plain, pre-lift form

Put the caret on if name? and length name > minLen, and right-click, or choose Refactor > Refactor This…, and choose Lift into Guard…

The EK9 IDE once this is done: Put the caret on if name? and length name > minLen, and right-click, or choose Refactor > Refactor This…, and choose Lift into Guard…

The preview shows the guard subsuming the redundant name? check, with the OTHER half of the condition - length name > minLen - kept as a 'then' clause: if name <- storedName() then length name > minLen. Choose Apply.

Step 6: Echo — the same guard in a 'while'

Choose Code > New Construct > Program…

The EK9 IDE once this is done: Choose Code > New Construct > Program…

Echo is the program's name. Type it and choose Preview.

The EK9 IDE just before choosing Preview: Echo is the program's name

The preview shows Echo about to be written. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows Echo about to be written

Select Echo() and above it type the comment - the blank line above it is already there. Saves when typed.

The EK9 IDE once this is done: Select Echo() and above it type the comment - the blank line above it is already there

Select the stub, from ready <- true down to its //AI line, and type the plain, pre-lift form: an iterator fetched once, into iter, then a while loop reading iter.hasNext() as its own, separate condition. Saves when typed.

The EK9 IDE once this is done: Select the stub, from ready <- true down to its //AI line, and type the plain, pre-lift form

Put the caret on while iter.hasNext(), and right-click, or choose Refactor > Refactor This…, and choose Lift into Guard…

The EK9 IDE once this is done: Put the caret on while iter.hasNext(), and right-click, or choose Refactor > Refactor This…, and choose Lift into Guard…

The preview shows iter <- lines.iterator() folded into the while head, evaluated once; the loop's own condition, iter.hasNext(), survives as a 'then' clause: while iter <- lines.iterator() then iter.hasNext(). Choose Apply.

Step 7: Numbered — the same guard before a 'for'

Choose Code > New Construct > Program…

The EK9 IDE once this is done: Choose Code > New Construct > Program…

Numbered is the program's name. Type it and choose Preview.

The EK9 IDE just before choosing Preview: Numbered is the program's name

The preview shows Numbered about to be written. Choose Apply.

The EK9 IDE just before choosing Apply: The preview shows Numbered about to be written

Select Numbered() and above it type the comment - the blank line above it is already there. Saves when typed.

The EK9 IDE once this is done: Select Numbered() and above it type the comment - the blank line above it is already there

Select the stub, from ready <- true down to its //AI line, and type the plain, pre-lift form: prefix declared from messagePrefix() as a one-off setup step, then a for loop with its own, separate iteration. Saves when typed.

The EK9 IDE once this is done: Select the stub, from ready <- true down to its //AI line, and type the plain, pre-lift form

Put the caret on for i in 1 ... 3, and right-click, or choose Refactor > Refactor This…, and choose Lift into Guard…

The EK9 IDE once this is done: Put the caret on for i in 1 ..

The preview shows prefix <- messagePrefix() folded into the for head, evaluated once; the loop's own iteration, i in 1 ... 3, survives as a 'then' clause: for prefix <- messagePrefix() then i in 1 ... 3. Choose Apply.

Step 8: Run it

Choose Run > Run Program…

The EK9 IDE once this is done: Choose Run > Run Program…

Choose Greet from the list, then choose Run.

Hello, Ada - lookupName() gave nothing, so its guard never ran; storedName() gave Ada, so its guard did. Choose Close.

The EK9 IDE just before choosing Close: Hello, Ada - lookupName() gave nothing, so its guard never ran; storedName() gave Ada, so its guard did

The result

Guards.ek9 now reads exactly as chapter 10 of Getting Started with EK9 shows it: three functions a program can ask for a name, and four programs that each declare one, check it is set, and act on it - written here plainer first, a declaration then a separate check right after it - and Lift into Guard folds the two into the construct's own guarded head every time: a plain if, an if with an extra condition kept as 'then', a while and a for whose own loop condition and iteration survive the fold the same way. The same tool - ek9_apply_guard_pattern - is what an AI assistant calls to do the same thing.

The EK9 IDE when the lesson has finished

Where this lesson comes from