Conditionals and Branching

A major component of computers is being able to make decisions. If you’ve worked with other programming languages, you’ll be familiar with common terms like if and else.

Let’s look at a quick example to introduce conditionals:

fr> : IS_TWO? ( n -- n ) dup 2 = if ." It's two!" then ;
ok.
fr> 2 is_two?
It's two! ok.

The complete definition is dup 2 = if ." xxx" then. We have a few new words we haven’t seen before here, so lets step through this definition:

  1. dup duplicates the top element of the stack
  2. 2 pushes a 2 onto the stack
  3. = pops the top two values and pushes TRUE if they’re equal and FALSE otherwise
  4. if checks if the top of stack is TRUE; if not, it skips to then
  5. ." xxx" prints xxx
  6. then signals the end of the if statement

Thus, we get the following behavior:

fr> 2 is_two?
It's two! ok.
fr> 1 is_two?
ok.

Note how in the second case, we have no output, since the value pushed is not equal to 2. It would be nice to have some output to tell us this, which is where else comes in. else executes statements only if the if branch did not execute. For example:

fr> : IS_TWO? ( n -- n ) dup 2 = if ." It's two!" else ." Not two :(" then ;
ok.
fr> 2 is_two?
It's two! ok.
fr> 1 is_two?
Not two :( ok.

then statements end the conditional. It’s important to note that everything after then will execute regardless of whether the if block executed or not. You must include a then to close an if statement, or else the interpreter won’t know where to skip to after the end of interpretation.

We can also nest if statements. For this example, we’ll use <, which functions exactly like = except that it pushes TRUE if a < b and FALSE otherwise.

fr> : is_big? ( a -- a ) dup 10 < if ." Small" else dup 20 < if ." Medium" else ." Big" then then ." number" cr ; 
ok.
fr> 5 is_big?
Small number 
ok.
fr> 15 is_big?
Medium number 
ok.
fr> 25 is_big?
Big number 
ok.

Writing long functions like this can be a little annoying. We can use the \ to break up lines without executing functions, which can help make them more readable:

fr> : is_big? ( a -- a ) \
  + dup 10 <             \
  + if                   \
  +    ." Small"         \
  + else dup 20 <        \
  +   if ." Medium"      \
  +   else ." Big"       \
  +   then               \
  + then                 \
  + ." number" ; 
ok.

The interpreter will add the + to signify that it’s waiting for you to finish the line. If you get stuck, use CTRL+C to quit.

So what is happening here? It’s a function that expects a number on the stack and doesn’t consume it.

  1. dup duplicates the number
  2. 10 < pushes TRUE if the number less than 10 and FALSE otherwise
  3. if TRUE on the stack, print “Small” and go to (8)
  4. dup duplicates the number
  5. 20 < pushes TRUE if the number less than 20 and FALSE otherwise
  6. if TRUE on the stack, print “Medium” and go to (8)
  7. print “Big”
  8. print “number”

Note: Every if needs exactly one then!

There are many comparators available, not just = and <:

if uses R’s as.logical to check if the top value evaluates to TRUE. This means that nonzero numbers will be treated as TRUE, whereas 0 will evaluate to false. Things that cannot be converted to logicals (e.g. 'a') will throw an error.

Logical Operators

Just like in other programming languages, froth supports a number of logical operators.

Some words come with built-in checks. For example, ?DUP duplicates the top value only if it is not zero. For error-handling, you can use ABORT". ABORT" checks the stack for a value; if it is TRUE, it clears the stacks and prints an error message.

Words in this chapter