Comp210: Principles of Computing and Programming
Fall 2004 -- Lecture #18   


Locals

Consider the following program to find the maximum depth of a binary tree:

;; A BinTree is either

;; -- EmptyTree

;; -- NETree



;; A NETree

;; (make-NETree any BinTree BinTree)

(define-struct NETree (first left right))



;; EmptyTree is empty

(define-struct EmptyTree ())









;;Looks kind of like a Family Tree doesn't it?



(define EMPTYTREE (make-EmptyTree))



(define bt (make-NETree 3 

                        (make-NETree 5

                                     EMPTYTREE

                                     (make-NETree 6 

                                                  EMPTYTREE 

                                                  EMPTYTREE))

                        (make-NETree 2 

                                     EMPTYTREE 

                                     EMPTYTREE)))



;; maxDepth_long: BinTree --> num

;; Returns the maximum depth of the tree.

(define (maxDepth_long binTree)

    (cond

        [(EmptyTree? binTree) 0]

        [(NETree? binTree) 

         (cond

             [(< (maxDepth_long (NETree-left binTree)) 

                 (maxDepth_long (NETree-right binTree))) 

              (add1 (maxDepth_long (NETree-right binTree)))]

             [else (add1  (maxDepth_long (NETree-left binTree)))])]))





"maxDepth_long test cases: "

(= 0 (maxDepth_long EMPTYTREE))

(= 1 (maxDepth_long (make-NETree 42 EMPTYTREE EMPTYTREE)))
(= 2 (maxDepth_long (make-NETree 42 (make-NETree 3 EMPTYTREE EMPTYTREE) (make-NETree 7 EMPTYTREE EMPTYTREE))))
(= 3 (maxDepth_long bt))

This is not a particularly efficient program -- it calculates the everything twice, once for the conditional and once for the actual answer. There's got to be a better way.

How bad is it? Let's analyze how many times maxDepth_long gets called during the recursion :
(note: this is not an exact calculation)

Thus if there are N nodes on the tree, maxDepth_long gets called about Nlog23 = N1.585 times. (For now, just believe us. Later courses will explain how to get that.)

Suppose we could arrange to only call maxDepth_long once on each sub-tree? Then, instead of 3 calls per node in the tree, we would only have 2 calls. The total number of calls would thus only be about Nlog22 = N calls. The savings would be a multiplicative factor of N1.585. If N is any size at all, this is a huge savings. It behooves us to find a way to accomplish this.

Let's look at some new syntax in Scheme, called "local".

To use local, set the language level to at least "Intermediate Student"

We'll check out what it does with some examples here:

(define x 2)

"x, before local"

x



"x, inside local"

(local 

    [(define x 5)]

    x)   ;; x has a different definition in here.



"x, after local"

x

The output from the above code is

x, before local

2

  

x, inside local

5

  

x, after local

2

Local has enabled a "local" redefinition of x.

Scope

The "scope" of a definition is the section of code in which that definition is valid.

Scoping is an enforcement of encapsulation.

Visibility

The ability, at a given point in the code, to be able to use a definition

Lack of visibility is a consequence of scoping enforced encapsulation.

 

Note that it is possible to be in scope but not visible.

To be visible however, one must be in scope.

local

Syntax:



(local

    [(define ...)

     (define ...)

     ...]

    expression)

Effects:

  • local creates a new scoping region in the code.
  • All definitions inside the local are scoped to only the extent of the local clause.
  • Definitions can for placeholders, functions or structures. Note that the constructor and accessors for structures share the same scoping as the structure definition.
  • All definitions that are already in scope are visible, unless they are overridden ("shadowed") by a new define statement.
  • No definitions from inside the local clause are visible outside of it, as they are not in scope anymore.
  • The result of the local clause is the result of the expression.

Purposes:

  • Creates and enforces encapsulation:
    • Hiding definitions from the code outside the local -- that is the outside code cannot see the definitions defined inside the local.
    • Create encapsulations of computations.
    • Hides helper functions and helper structures.
  • Makes the code more understandable
    • By encapsulating calculations into simple entities.
    • By enabling the expression of more complicated calculations in terms of simpler blocks.
  • Makes code more effecient by eliminating redundant calculations.

Let's look at another example:

(define z 10)





(define (local_test1 x)

    (cond 

        [(< x 5) (local [(define a 2)]

                        (+ z (* x a)))]

        [else (+ z (* x a))]))



" local_test1: This test works: "

(= 16 (local_test1 3))



;"local_test1:  This test will not execute at all."

;(= 22 (local_test1 6))

Scoping and visibility of the various placeholders:

The second test gives an error because a is not scoped to the else clause.

So what would our maxDepth function look like using a local clause?

;; maxDepth: BinTree --> num

;; Returns the maximum depth of the tree.

(define (maxDepth binTree)

    (cond

        [(EmptyTree? binTree) 0]

        [(NETree? binTree) 

         (local

             [(define leftDepth (maxDepth (NETree-left binTree)))

              (define rightDepth (maxDepth (NETree-right binTree)))]

             (cond

                 [(< leftDepth rightDepth) (add1 rightDepth)]

                 [else (add1 leftDepth)]))]))



"maxDepth test cases:"

(= 0 (maxDepth EMPTYTREE))

(= 1 (maxDepth (make-NETree 42 EMPTYTREE EMPTYTREE)))
(= 2 (maxDepth (make-NETree 42 (make-NETree 3 EMPTYTREE EMPTYTREE) (make-NETree 7 EMPTYTREE EMPTYTREE))))
(= 3 (maxDepth bt))

Piece of cake.

For the record, the actual savings for the number of calls to the function are:

Tree
# of calls to maxDept_long
# calls to maxDepth
EMPTYTREE
1
1
(make-NETree 42 EMPTYTREE EMPTYTREE)
4
3
(make-NETree 42 (make-NETree 3 EMPTYTREE EMPTYTREE)

                (make-NETree 7 EMPTYTREE EMPTYTREE)))
13
7
bt
25
9

 

local is also handy when you have many sub-expressions building up your answer.

;; earth-pull: given a mass which is "height" meters above the ground (mean radius of the Earth),

;; return the force of gravity between it and the earth, in Newtons.
;; (define (earth-pull m height) (local [(define G 6.67e-11) ; Universal gravitational constant in SI units. (define earth-mass 5.97e24) ; in kg (define earth-radius 6380000) ; in m (define dist-from-center (+ earth-radius height))] (/ (* G earth-mass m) (square dist-from-center)))) "earth-pull test cases (1% accuracy):" (> .01 (abs (/ (- 9.8 (earth-pull 1 0)) 9.8))) (> .01 (abs (/ (- 166.30 (earth-pull 17 23)) 166.30)))
As per the First Law of Programming, the calculation is now expressed in terms that make sense, not magic numbers.

Let's see how local can help tame the proliferation of helper functions we are experiencing:

;; sum: lon --> num
;; sums the list of numbers
(define (sum lon)
(cond
[(empty? lon) 0]
[(cons? lon)
(local
(;;helper: lon num --> num
;; returns the sum of the lon plus the sum-so-far.
(define (helper lon sum-so-far)
(cond
[(empty? lon) sum-so-far]
[(cons? lon) (helper (rest lon) (+ (first lon) sum-so-far))])))
(helper (rest lon) (first lon)))]))
"sum test cases:" (= 0 (sum empty)) (= 15 (sum (list 1 2 3 4 5)))

The helper function is not visible to the outside world -- all that is seen is a nicely encapsulated sum function.

Today's code can be downloaded here: local.scm

Both Prof. Barland and Prof. Cooper wrote nice lectures on local. I recommend that you go read them:

Prof. Barland's Fall 2001 local lecture

Prof. Cooper's Spring 2002 local lecture

 


Last Revised Tuesday, 24-Aug-2004 13:49:00 CDT

©2004 Stephen Wong and Dung Nguyen