Prev Index Next

Conditionals (basic)

In a computer program described by sequences of expressions, it is often the case that a decision has to be made between two possible expressions. In such cases, a conditional expression selects which of these two expressions evaluates next.

This chapter introduces conditional expressions and shows if expressions, logical operators, predicates, when, unless, and cond expressions.

if expressions

Suppose you have a test to select between two choices. If the test is true, then one choice is made, othewise, the alternative is made. In Scheme, such a method of decision making is called an if expression.

To write an if expression, you start with parentheses, where the first element is the if identifier, the second element is the test, the third element is the consequent, and the final fourth element is the alternative. In other words, an if expression is in the form of: (if <test> <consequent> <alternative>).

But before I describe evaluation of if, recall that false is #f and true is #t and both of these are self-evaluating expressions:

> #t
$1 = #t
> #f
$2 = #f

An if expression is evaluated as follows. First, the <test> is evaluated. If it yields a true value, then <consequent> is evaluated and its value is returned. Otherwise, <alternative> is evaluated and its value is returned.

> (if #t 1 2)
$3 = 1
> (if #f 1 2)
$4 = 2

An if expression evaluates either the <consequent> or the <alternative>, but never both. This is very important. In Scheme, an if expression evaluates in normal order, unlike other symbolic expressions, which evaluate in applicative order.

Note that the <alternative> is optional. When <test> evaluates to #f and the <alternative> is omitted, the expression result is undefined.

The if primitive expression is the fundamental conditional in Scheme, out of which all other conditionals, such as cond, case, when, or lambda-case, are built upon.

Often if or any of its above-mentioned derivations are used in combination with logical operators and predicates, both of which are described next.

Logical operators

There are at least three different operations that can be applied to #f and #t values. The first operation is the procedure not, and the other two operations are the macros and and or.

> not
$1 = #<procedure not (_)>

Let us implement my-not by writing a procedure that accepts one argument, denoted by z:

(define (my-not z)

An if expression is used to test whether the argument z is true and it then returns false:

(define (my-not z)
  (if z #f #t))

> (my-not 1)
$1 = #f
> (my-not #t)
$2 = #f
> (my-not #f)
$3 = #t

It is revealing to see that #f and #t can be implemented as procedures. This is not how these are implemented in Guile. Rather the purpose here is to show that you can implement not, true, and false without using any data structures at all but only using procedures.

Let us start with the indentity procedure, which accepts x and returns x:

(lambda (x) x)

Suppose you add one more argument, denoted by y. The return value can now be either ‘x’ or ‘y’:

(lambda (x y) x)
(lambda (x y) y)

Consider what happens when a not expression is called with an argument z. If z is true, then return false, otherwise return true. This is similar to the above-shown identity procedures with two arguments.

Suppose the following definitions:

> (define my-true (lambda (x y) x))
> (define my-false (lambda (x y) y))

Now simply call these functions with themselves as arguments:

> (my-true my-false my-true)
$4 = #<procedure my-false (x y)>
> (my-false my-false my-true)
$5 = #procedure my-true (x y)>

Clearly, the not operation is:

> (define (my-not z) (z my-false my-true))
> (my-not my-false)
$6 = #<procedure my-true (x y)>
> (my-not my-true)
$7 = #<procedure my-false (x y)>

Unlike not which is a procedure, the and and or operations are implemented as macros. To understand why, consider the following example:

(and #t #f <exp-4>)

In Scheme, the arguments of and expressions are evaluated left-to-right, until a false value is reached or no more expressions are found. In the example above, <exp-4> is never evaluated because the expression to the left evaluates into #f. This is why and is a macro, and not a procedure, which would evaluate <exp-4> regardless of the expressions to the left.

So, the following two expressions do not result in an error

> (and #t #f (4 does not error as it never evaluates))
$8 = #f

> (or #f #t (4 does not error as it never evaluates))
$9 = #t

To this end, you are not ready to write my-and and my-or as macros, because we did not learn recursion. I leave this for a later chapter.

Predicates

Recall that the if expression must contain a test, whose outcome determines the program flow. The Scheme standards define various tests for expressions. We call such tests predicates. Here are eight examples:

> (procedure? +)
#1 = #t

> (procedure? (lambda (x) x))
$2 = #t

> (number? 37)
$3 = #t

> ((lambda (x) (and (number? x) (even? x))) 50)
$4 = #t

> ((lambda (x) (and (number? x) (odd? x))) 50)
#5 = #f

> (zero? 37)
#6 = #f

> (and (boolean? #f) (boolean? #t))
$7 = #t

> (boolean? 73)
$8 = #f

A predicate is a procedure that returns true or false. They conventionally have names ending with a question mark. Predicates are often used in combination with if expressions:

> ((lambda (x) (if (even? x) (/ x 2) x)) 73)
$9 = 73
> ((lambda (x) (if (even? x) (/ x 2) x)) 48)
$10 = 24

Let us now see expressions derived using if expressions.

when and unless

Recall that expressions have a result and a side effect. Sometimes, of interest is the side effect but not the result of an if expression. In Scheme, you clearly express this idea by using a when expression, which is simply an if expression without an <alternative>. Similarly, unless is if without an <alternative>, but with a negated <test>.

> ((lambda (x) (when (even? x) x)) 48)
$1 = 48
> ((lambda (x) (unless (even? x) x)) 73)
$2 = 73

Both when and unless are macros which cannot be implemented as procedures. The reason why when and unless cannot be procedures is that the <consequent> expression is not always evaluated, depending on the result of the test. But a procedure always evaluates all arguments, meaning that a procedure implementation of when and unless would always evaluate the <consequent>, regardless of the test, which is not the desired behaviour.

Let us write my-when as a macro. As always, begin with:

(define-syntax my-when (lambda (x) (syntax-case x ()

The expression before expansion (EBE) is simply the form of the when expression:

(define-syntax my-when (lambda (x) (syntax-case x ()
  ((my-when test exp-1 exp* ...)

The expression after expansion (EAE) is:

(define-syntax my-when (lambda (x) (syntax-case x ()
  ((my-when test exp-1 exp* ...)
   #'(if test (begin exp-1 exp* ...))))))

Let us write my-unless as a macro. As always, begin with:

(define-syntax my-unless (lambda (x) (syntax-case x ()

The EBE is simply the form of the unless expression:

(define-syntax my-unless (lambda (x) (syntax-case x ()
  ((my-unless test exp-1 exp* ...)

The EAE is simply the form of the when expression:

(define-syntax my-unless (lambda (x) (syntax-case x ()
  ((my-unless test exp-1 exp* ...)
   #'(if (not test) (begin exp-1 exp* ...))))))

Let us expand my-when and my-unless:

> ,expand (my-when (even? x) x)
$3 = (if (even? x) x)

> ,expand (my-unless (even? x) x)
$4 = (if (not (even? x)) x)

Cond

Sometimes many if expressions are chained together. Starting from the parent if the child if is the <alternative> expression.

Here is an example of a level-three chain of if expressions

(if <test-1>
    <consequent-1>
    (if <test-2>
        <consequent-2>
        (if <test-3>
            <consequent-3>
            <alternative>)))

The Scheme standards define the cond macro (derived expression). Here is how to rewrite the previous three-level if-chain into a cond derived expression

(cond
  (<test-1> <consequent-1>)
  (<test-2> <consequent-2>)
  (<test-3> <consequent-3>)
  (else <alternative>))

To this end, you are not ready to write my-cond as a macro, because you did not learn recursion nor let expressions.

Prev Index Next