3.5. Internal Definitions


3.5. Internal Definitions

In Section 2.6, we discussed top-level definitions. Definitions may also appear at the front of a lambda, let, or letrec body, in which case the bindings they create are local to the body.

 (define f (lambda (x) (* x x))) (let ((x 3))   (define f (lambda (y) (+ y x)))   (f 4))  7 (f 4)  16 

Procedures bound by internal definitions can be mutually recursive, as with letrec. For example, we can rewrite the even? and odd? example from Section 3.2 using internal definitions as follows.

 (let ()   (define even?     (lambda (x)       (or (= x 0)           (odd? (- x 1)))))   (define odd?     (lambda (x)       (and (not (= x 0))            (even? (- x 1))))) (even? 20))  #t 

Similarly, we can replace the use of letrec to bind race with an internal definition of race in our first definition of list?.

 (define list?   (lambda (x)     (define race       (lambda (h t)         (if (pair? h)             (let ((h (cdr h)))               (if (pair? h)                   (and (not (eq? h t))                        (race (cdr h) (cdr t)))                   (null? h)))             (null? h))))     (race x x))) 

In fact, internal definitions and letrec are practically interchangeable. It should not be surprising, therefore, that a lambda, let, or letrec body containing internal definitions can be replaced with an equivalent letrec expression. A body of the form

 (define var val) . . . exp1 exp2 . . . 

is equivalent to a letrec expression binding the defined variables to the associated values in a body comprising the expressions.

 (letrec ((var val) ...) exp1 exp2 ...) 

Conversely, a letrec of the form

 (letrec ((var val) ...) exp1 exp2 ...) 

can be replaced with a let expression containing internal definitions and the expressions from the body as follows.

 (let ()   (define var val)     .     .     .   exp1   exp2     .     .     . ) 

The seeming lack of symmetry between these transformations is due to the fact that letrec expressions can appear anywhere an expression is valid, whereas internal definitions can appear only at the front of a body. Thus, in replacing a letrec with internal definitions, we must generally introduce a let expression to hold the definitions.

Syntax definitions may also appear at the front of a lambda, let, or letrec body.

 (let ((x 3))   (define-syntax set-x!     (syntax-rules ()       (( e) (set! x e))))   (set-x! (+ x x))   x)  6 

The scope of a syntactic extension established by an internal syntax definition, as with an internal variable definition, is limited to the body in which the syntax definition appears.

Internal definitions may be used in conjunction with top-level definitions and assignments to help modularize programs. Each module of a program should make visible only those bindings that are needed by other modules, while hiding other bindings that would otherwise clutter the top-level namespace and possibly result in unintended use or redefinition of those bindings. A common way of structuring a module is shown below.

 (define export-var #f)   .   .   . (let ()   (define var val)     .     .     .   init-exp     .     .     .   (set! export-var export-val)     .     .     . ) 

The first set of definitions establish top-level bindings for the variables we desire to export (make visible globally). The second set of definitions establish local bindings visible only within the module. The expressions init-exp perform any initialization that must occur after the local bindings have been established. Finally, the set! expressions assign the exported variables to the appropriate values. Some of the extended examples in Chapter 9 use this modularization technique.

One advantage of this form of modularization is that the bracketing let expression may be removed or "commented out" during program development, making the internal definitions top-level to facilitate interactive testing.

The following module exports a single variable, calc, which is bound to a procedure that implements a simple four-function calculator.

 (define calc #f) (let ()   (define do-calc     (lambda (ek exp)       (cond         ((number? exp) exp)         ((and (list? exp) (= (length exp) 3))          (let ((op (car exp)) (args (cdr exp)))            (case op              ((add) (apply-op ek + args))              ((sub) (apply-op ek - args))              ((mul) (apply-op ek * args))              ((div) (apply-op ek / args))              (else (complain ek "invalid operator" op)))))          (else (complain ek "invalid expression" exp)))))   (define apply-op     (lambda (ek op args)       (op (do-calc ek (car args)) (do-calc ek (cadr args)))))   (define complain     (lambda (ek msg exp)       (ek (list msg exp))))   (set! calc     (lambda (exp)       ; grab an error continuation ek       (call/cc         (lambda (ek)           (do-calc ek exp)))))) (calc '(add (mul 3 2) -4))  2 (calc '(div 1/2 1/6))  3 (calc '(add (mul 3 2) (div 4)))  ("invalid expression" (div 4)) (calc '(mul (add 1 -2) (pow 2 7)))  ("invalid operator" pow) 

This example uses a case expression to determine which operator to apply. case is similar to cond except that the test is always the same: (memv val (key )), where val is the value of the first case subform and (key ) is the list of items at the front of each case clause. The case expression in the example above could be rewritten using cond as follows.

 (let ((temp op))   (cond     ((memv temp '(add)) (apply-op ek + args))     ((memv temp '(sub)) (apply-op ek - args))     ((memv temp '(mul)) (apply-op ek * args))     ((memv temp '(div)) (apply-op ek / args))     (else (complain ek "invalid operator" op)))) 

Exercise 3.5.1.

start example

Redefine complain in the calc example as an equivalent syntactic extension.

end example

Exercise 3.5.2.

start example

In the calc example, the error continuation ek is passed along on each call to apply-op, complain, and do-calc. Move the definitions of apply-op, complain, and do-calc inward as far inward as necessary to eliminate the ek argument from the definitions and applications of these procedures.

end example

Exercise 3.5.3.

start example

Eliminate the call/cc from calc and rewrite complain to signal an error, using the error-reporting facilities provided by your Scheme implementation.

end example

Exercise 3.5.4.

start example

Extend calc to handle unary minus expressions, e.g.,

 (calc '(minus (add 2 3)))  -5 

and other operators of your choice.

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