So far we have
(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
(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
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
To be more precise, in case (a), the value of the
or
expression is the value of the last subexpression evaluated. This
(< -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
(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
(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
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
(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.
|
|
Define the predicate atom? , which returns true if its argument is not a pair and false if it is.
|
|
Exercise 2.7.2.
|
|
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)
|
|