2.7. Conditional Expressions


2.7. Conditional Expressions

So far we have considered expressions that perform a given task unconditionally. Suppose that we wish to write the procedure abs. If its argument x is negative, abs returns x; otherwise, it returns x. The most straightforward way to write abs is to determine whether the argument is negative and if so negate it, using the if syntactic form.

 (define abs   (lambda (n)     (if (< n 0)         (- 0 n)         n))) (abs 77)  77 (abs -77)  77 

An if expression has the form (if test consequent alternative), where consequent is the expression to evaluate if test is true and alternative is the expression to evaluate if test is false. In the expression above, test is (< n 0), consequent is (- 0 n), and alternative is n.

The procedure abs could be written in a variety of other ways. Any of the following are valid definitions of abs.

 (define abs   (lambda (n)     (if (>= n 0)         n         (- 0 n)))) (define abs   (lambda (n)     (if (not (< n 0))         n         (- 0 n)))) (define abs   (lambda (n)     (if (or (> n 0) (= n 0))         n         (- 0 n)))) (define abs   (lambda (n)     (if (= n 0)         0         (if (< n 0)             (- 0 n)             n)))) (define abs   (lambda (n)     ((if (>= n 0) + -)      0      n))) 

The first of these definitions asks if n is greater than or equal to zero, inverting the test. The second asks if n is not less than zero, using the procedure not with <. The third asks if n is greater than zero or n is equal to zero, using the syntactic form or. The fourth treats zero separately, though there is no benefit in doing so. The fifth is somewhat tricky; n is either added to or subtracted from zero, depending upon whether n is greater than or equal to zero.

Why is if a syntactic form and not a procedure? In order to answer this, let's revisit the definition of reciprocal from the first section of this chapter.

 (define reciprocal   (lambda (n)     (if (= n 0)         "oops!"         (/ 1 n)))) 

When the second argument to the division procedure is zero, the behavior is unspecified, and many implementations signal an error. Our definition of reciprocal avoids this problem by testing for zero before dividing. Were if a procedure, its arguments (including (/ 1 n)) would be evaluated before it had a chance to choose between the consequent and alternative. Like quote, which does not evaluate its only subexpression, if does not evaluate all of its subexpressions and so cannot be a procedure.

The syntactic form or operates in a manner similar to if. The general form of an or expression is (or exp ). If there are no subexpressions, i.e., the expression is simply (or), the value is false. Otherwise, each exp is evaluated in turn until either (a) one of the expressions evaluates to true or (b) no more expressions are left. In case (a), the value is true; in case (b), the value is false.

To be more precise, in case (a), the value of the or expression is the value of the last subexpression evaluated. This clarification is necessary because there are many possible true values. Usually, the value of a test expression is one of the two objects #t, for true, or #f, for false.

 (< -1 0)  #t (> -1 0)  #f 

Every Scheme object, however, is considered to be either true or false by conditional expressions and by the procedure not. Only #f is considered false; all other objects are considered true.

 (if #t 'true 'false)  true (if #f 'true 'false)  false (if '() 'true 'false)  true (if 1 'true 'false)  true (if '(a b c) 'true 'false)  true (not #t)  #f (not "false")  #f (not #f)  #t (or)  #f (or #f)  #f (or #f #t)  #t (or #f 'a #f)  a 

The and syntactic form is similar in form to or, but an and expression is true if all its subexpressions are true, and false otherwise. In the case where there are no subexpressions, i.e., the expression is simply (and), the value is true. Otherwise, the subexpressions are evaluated in turn until either no more subexpressions are left or the value of a subexpression is false. The value of the and expression is the value of the last subexpression evaluated.

Using and, we can define a slightly different version of reciprocal.

 (define reciprocal   (lambda (n)     (and (not (= n 0))          (/ 1 n)))) (reciprocal 3)  1/3 (reciprocal 0.5)  2.0 (reciprocal 0)  #f 

In this version, the value is #f if n is zero and 1/n otherwise.

The procedures =, <, >, <=, and >= are called predicates. A predicate is a procedure that answers a specific question about its arguments and returns one of the two values #t or #f. The names of most predicates end with a question mark ( ? ); the common numeric procedures listed above are exceptions to this rule. Not all predicates require numeric arguments, of course. The predicate null? returns true if its argument is the empty list () and false otherwise.

 (null? '())  #t (null? 'abc)  #f (null? '(x y z))  #f (null? (cdddr '(x y z)))  #t 

It is an error to pass the procedure cdr anything other than a pair, and most implementations signal an error when this happens. Common Lisp, however, defines (cdr '()) to be (). The following procedure, lisp-cdr, is defined using null? to return () if its argument is ().

 (define lisp-cdr   (lambda (x)     (if (null? x)         '()         (cdr x)))) (lisp-cdr '(a b c))  (b c) (lisp-cdr '(c))  () (lisp-cdr '())  () 

Another useful predicate is eqv?, which requires two arguments. If the two arguments are equivalent, eqv? returns true. Otherwise, eqv? returns false.

 (eqv? 'a 'a)  #t (eqv? 'a 'b)  #f (eqv? #f #f)  #t (eqv? #t #t)  #t (eqv? #f #t)  #f (eqv? 3 3)  #t (eqv? 3 2)  #f (let ((x "Hi Mom!"))   (eqv? x x))  #t (let ((x (cons 'a 'b)))   (eqv? x x))  #t (eqv? (cons 'a 'b) (cons 'a 'b))  #f 

As you can see, eqv? returns true if the arguments are the same symbol, boolean, number, pair, or string. Two pairs are not the same by eqv? if they are created by different calls to cons, even if they have the same contents. Detailed equivalence rules for eqv? are given in Section 6.2.

Scheme also provides a set of type predicates that return true or false depending on the type of the object, e.g., pair?, symbol?, number?, and string?. The predicate pair?, for example, returns true only if its argument is a pair.

 (pair? '(a . c))  #t (pair? '(a b c))  #t (pair? '())  #f (pair? 'abc)  #f (pair? "Hi Mom!")  #f (pair? 1234567890)  #f 

Type predicates are useful for deciding if the argument passed to a procedure is of the appropriate type. For example, the following version of reciprocal checks first to see that its argument is a number before testing against zero or performing the division.

 (define reciprocal   (lambda (n)     (if (and (number? n) (not (= n 0)))         (/ 1 n)         "oops!"))) (reciprocal 2/3)  3/2 (reciprocal 'a)  "oops!" 

By the way, the code that uses reciprocal must check to see that the returned value is a number and not a string. It is usually better to report the error, using whatever error-reporting facilities your Scheme implementation provides. For example, Chez Scheme provides the procedure error for reporting errors; we might use error in the definition of reciprocal as follows.

 (define reciprocal   (lambda (n)     (if (and (number? n) (not (= n 0)))         (/ 1 n)         (error 'reciprocal "improper argument ~s" n)))) (reciprocal .25)  4.0 (reciprocal 0)  Error in reciprocal: improper argument 0. (reciprocal 'a)  Error in reciprocal: improper argument a. 

The first argument to error is a symbol identifying where the message originates, the second is a string describing the error, and the third and subsequent arguments are objects to be inserted into the error message. The message string must contain one ~s for each object; the position of each ~s within the string determines the placement of the corresponding object in the resulting error message.

Let's consider one more conditional expression, cond, that is often useful in place of if. cond is similar to if except that it allows multiple test and alternative expressions. A cond expression usually takes the following form.

 (cond (test exp) ... (else exp)) 

Consider the following definition of sign, which returns 1 for negative inputs, +1 for positive inputs, and 0 for zero.

 (define sign   (lambda (n)     (if (< n 0)         -1         (if (> n 0)             +1             0)))) (sign -88.3)  -1 (sign 0)  0 (sign 333333333333)  1 (* (sign -88.3) (abs -88.3))  -88.3 

The two if expressions may be replaced by a single cond expression as follows.

 (define sign   (lambda (n)     (cond       ((< n 0) -1)       ((> n 0) +1)       (else 0)))) 

Sometimes it is clearer to leave out the else clause. This should be done only when there is no possibility that all the tests will fail, as in the new version of sign below.

 (define sign   (lambda (n)     (cond       ((< n 0) -1)       ((> n 0) +1)       ((= n 0) 0)))) 

These definitions of sign do not depend on the order in which the tests are performed, since only one of the tests can be true for any value of n. The following procedure computes the tax on a given amount of income in a progressive tax system with breakpoints at 10,000, 20,000, and 30,000 dollars.

 (define income-tax   (lambda (income)     (cond       ((<= income 10000) (* income .05))       ((<= income 20000) (+ (* (- income 10000) .08) 500.00))       ((<= income 30000) (+ (* (- income 20000) .13) 1300.00))       (else (+ (* (- income 30000) .21) 2600.00))))) (income-tax 5000)  250.0 (income-tax 15000)  900.0 (income-tax 25000)  1950.0 (income-tax 50000)  6800.0 

In this example, the order in which the tests are performed, left to right (top to bottom), is significant.

Exercise 2.7.1.

start example

Define the predicate atom?, which returns true if its argument is not a pair and false if it is.

end example

Exercise 2.7.2.

start example

The procedure length returns the length of its argument, which must be a list. For example, (length '(a b c)) is 3. Using length, define the procedure shorter, which returns the shorter of two list arguments. Have it return the first list if they have the same length.

 (shorter '(a b) '(c d e))  (a b) (shorter '(a b) '(c d))  (a b) (shorter '(a b) '(c))  (c) 
end example




The Scheme Programming Language
The Scheme Programming Language
ISBN: 026251298X
EAN: 2147483647
Year: 2003
Pages: 98

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net