|
Comp210: Principles of Computing and Programming
|
A function definition
(define (parameters...) body)introduces a new local scope -- the parameters are defined within the body.
A local definition
(local [definitions...] body)introduces a new local scope -- the names in the definitions are defined within both within the bodies of the definitions and within the
local
's
body.
In order to use local
and to use functions as data (also
just introduced in class), you need to set the DrScheme language level to
"Advanced".
Unfortunately, DrScheme's stepper doesn't work beyond the Beginner level.
Let's consider the problem of finding the maximum number in a list. To simplify the problem for today's goal, assume the maximum number in an empty list is 0. (Previously, we've examined ways of writing this function only for non-empty lists.)
|
Using local
doesn't let us write programs which were impossible
before, but it does let us write them better.
How can you take any program written with
Your answer will show that even without using an accumulator or |
We'll return to an example seen in a previous lab. We'll develop functions returning the list of positive numbers 1...n (left-to-right), given n as input.
|
local
These examples point out the reasons why to use local
:
Avoid code duplication. I.e., increase code reuse.
As in the maximum element of a list example.
Avoid recomputation.
Sometimes provides dramatic benefits, as with the maximum element of a list example.
Avoid re-mentioning an unchanging argument.
As in the ascending numbers list example.
To hide the name of helper functions.
As in the ascending numbers list example.
While important conceptually, most programming languages (including Scheme) provide alternate mechanisms for this which scale better to large programs. These mechanisms typically have names like modules or packages. As this one example shows, even small programs can get a bit less clear when hiding lots of helpers. |
Name individual parts of a computation, for clarity.
A side benefit in the maximum element of a list example.
On the other hand, don't get carried away. Here are two easy
ways to misuse local
:
;; max-of-list: non-empty-list --> number ;; (define (max-of-list a-nelon) (local [(define max-rest (max-of-list (rest a-nelon)))] (cond [(empty? (rest a-nelon)) (first a-nelon)] [else (cond [(< (first a-nelon) max-rest) max-rest] [else (first a-nelon)])])))
What's wrong with this? |
Make sure you don't put a local
too early in your code.
;; max-of-list: non-empty-list --> number ;; (define (max-of-list a-nelon) (cond [(empty? (rest a-nelon)) (first a-nelon)] [else (local [(define max-rest (max-of-list (rest a-nelon))) (define first-smaller? (< (first a-nelon) max-rest))] (cond [first-smaller? max-rest] [else (first a-nelon)]))]))
This isn't wrong, but the local definition of
first-smaller?
is unnecessary. Since the comparison is only
used once, this is not a case of code reuse or recomputation. It
provides a name to a part of the computation, but whether that improves
clarity in this case is a judgement call.
local
How does a local
evaluate? The following is one way to understand
it.
For each define
d name in the list of definitions, rename it
something entirely unique, consistently through the entire
local
.
Lift those defines to the top level, and erase the surrounding
local
, leaving only the body-expression.
Example:
(define x 5) (local [(define x 7)] x)Becomes:
(define x 5) (local [(define x^ 7)] x^)which in turn evaluates to
(define x 5) (define x^ 7) x^This makes it clear that there are really two independent placeholders.
Example:
(define x 5) (define y x) (define z (+ x y)) (local [(define x 7) (define y x)] (+ x y z))Evaluates to:
(define x 5) (define y x) (define z (+ x y)) (define x^ 7) (define y^ x^) (+ x^ y^ z)
As an equivalent way of looking at things, we can look at the original code and ask which definition corresponds to each placeholder use. The answer is simple -- it is always the closest (in terms of nestedness) enclosing binding definition. We have three forms of binding:
local
around everything.)
The actual way |
DrScheme provides an easy way to look at this: the Check Syntax button. Clicking on this does two things. First, it checks for syntactic correctness of your program in the definitions window. If there are errors, it reports the first one. But, if there aren't any, it then annotates your code with various colors and, when you move your cursor on top of a placeholder, draws arrows between placeholder definitions and uses.
In each case, |
In each of the following, determine which definition corresponds to each placeholder usage. As a result, determine what each example produces. Then confirm by using DrScheme. (Note: Some examples give errors about unbound placeholders.)
|
Last Revised Tuesday, 24-Aug-2004 13:49:06 CDT
©2004 Stephen Wong and Dung Nguyen