CS 1321
CS1321: Introduction to Programming Georgia Institute of Technology College of Computing Lecture 16 October 18, 2001 Fall Semester
Today’s Menu 1.Your Readings… 2.Intermezzo III 3.Similarities in Functions 4.Revising the way we think 1.Passing Values 2.Passing Functions 5.Changes to our design recipes
Intermezzo III and Onwards Everything changes, but nothing is truly lost. -Ovid (Omnia Mutantur, Nihil Interit) In the next few chapters of the book, we will be introduced to material that concerns itself less with the way that we represent data, and more with how we write the code that deals with our data. We will be exploring new concepts that will let you as the coder “get away” with more neat tricks than you could previously.
However… The introduction of whiz-bang new programming concepts that give you all sorts of short cuts does NOT mean you can ignore the lessons learned from the previous lectures. In many cases, you’ll find that the lessons learned will be even more important.
Where we are in the book… The material we will be covering for the next two to three lectures will come from: Chapter 18 (Intermezzo III) Chapters 19 through 22 Chapters 19 through 22 can be considered to be one giant chapter divided into four parts. These sections of the book contain many excellent examples. You are highly encouraged to read ahead!
Intermezzo III: (local …) Chapter 18 introduces us to a new function: (local …) The function local creates within our Scheme environment a small area where we can define new “variables”, structures or even functions. These new definitions exist and can be used ONLY within this new area we’ve defined. This is also known as restricting the “scope” of our definitions.
The Concept of Scope Global Scope Here, within this box, there exists data and rules and definitions that are “universal” to our problem. Anyone can view these items, use them, and perhaps even change them. Local Scope 1 Now we’ve created a new area to play in. We can create new pieces of data, rules, and definitions that exists only within the confines of this box. We can still access all the information within the global scope as we are still considered to be “within” the global scope. Local Scope 2 A new area to Play in Local Scope 3 A new area to Play in Local Scope 4
Confused? Here’s A more Visual explanation of what’s going on…
CITY OF ATLANTA CITY OF ATHENS At each level there are different sets of rules and data that are “scoped” to the area we’re considering
The syntax of local ( local ( …. )
The syntax of local ( local ( …. ) Here we define several local definitions that exist only within the local. When the local finishes evaluating, these disappear.
The syntax of local ( local ( …. ) Local, like all functions we’ve seen in Scheme, resolves to a value. In this case, it resolves to the value returned from our expression.
An example… When We Hit Execute…
What’s happening here? We’ve created a local scope in which we define the variable x. The local has to resolve to a value, so we decide to return the value of x. Right after the local finishes, we then try to evaluate x again.
Here, when we press “Execute”, Scheme evaluates first the call to local and then x
Another Example In this example, we define two items within local: the function my-func and the variable my-var. As our code to be executed by the local, we call the locally defined function passing it the locally defined variable. After the local finishes execution, we try to call my- func
Again, we see the evaluation of the local expression succeeds admirably. However, the call to my-func outside the local fails just as miserably as it did before.
A Last Example
Here we create two internal helper functions to a main function that we’re writing. As we’ll see in the next slide, these functions are only “visible” to the main function.
So I bet by now that you all have some questions about the stuff you’ll be allowed to get away with using local. Let’s try to address some of them:
Do I have to have complete Design Recipes for every helper function that I write as a local function? No, you do not HAVE to. These functions are “invisible” to a user, who may only be concerned with making use of your main functions, not knowing the internal details of how you calculate a result. Design Recipes are still required for your global functions. Hooray! No more commenting all the time! Not so! The value of comments within your code has not been diminished by this new discovery. So we will expect at the very least a Contract and Purpose section for every Internal function.
An Example
Ok, so I still have to comment. But I can still use local to get away with tons! I mean, I never have to have helper functions in the global definition area again! Not so! Again, that’s not quite the case. There will be situations where you will develop a helper function that will be needed by several, if not all, of the “main” functions you’re writing. You must not duplicate that exact same helper function in every single “main” function that needs to use it. If you’re writing a function that is called by more than one other function, put that helper in the global space and do a full Design Recipe! When to not make a helper local!
The Real Deal Using our new-found knowledge of local, re-write the insertion-sort function we developed several lectures ago. You should produce only ONE global function.
We start as we normally would ;; Data Analysis and Definition ;; A list-of-numbers is either: ;; 1) the empty list empty ;; 2) (cons n lon) where n is a number and lon is a ;; list-of-numbers ;; Contract: sort: list-of-numbers list-of-numbers ;; Purpose: This function takes in a list of numbers ;; and performs an insertion-sort on the numbers ;; to put the number into ascending order.
Continuing… ;; Examples: (sort empty) should produce empty ;; (sort (list 1 2)) should produce (list 1 2) ;; (sort (list )) should produce ;; (list ) ;;Template: ;; (define (process-lon in-lon) ;; (cond ((empty? in-lon) …) ;; (else …(first in-lon) ;; …(process-lon (rest in-lon))
The definition: one possible solution Let’s say that we want to deviate as LITTLE as possible from the way we originally wrote our sorting function. As we only had one helper function (namely insert), we could create the following definition:
(define (sort in-lon) (local ((define (insert value in-lon) (cond ((empty? in-lon) (list value)) (else (cond ((> value (first in-lon)) (cons (first in-lon) (insert value (rest in-lon)))) (else (cons value in-lon))))))) (cond ((empty? in-lon) empty) (else (insert (first in-lon) (sort (rest in-lon)))))))
(define (sort in-lon) (local ((define (insert value in-lon) (cond ((empty? in-lon) (list value)) (else (cond ((> value (first in-lon)) (cons (first in-lon) (insert value (rest in-lon)))) (else (cons value in-lon))))))) (cond ((empty? in-lon) empty) (else (insert (first in-lon) (sort (rest in-lon))))))) This is the local definition of an “insert” function
(define (sort in-lon) (local ((define (insert value in-lon) (cond ((empty? in-lon) (list value)) (else (cond ((> value (first in-lon)) (cons (first in-lon) (insert value (rest in-lon)))) (else (cons value in-lon))))))) (cond ((empty? in-lon) empty) (else (insert (first in-lon) (sort (rest in-lon))))))) This code at the bottom is the actual body of the “sort function.
Using this approach… Anyone who wants to use your code can ONLY call the “sort” function, not the “insert” function.
We could have also written it… (define (sort in-lon) (local ((define (insert-sort in-lon) (cond ((empty? in-lon) empty) (else (insert (first in-lon) (insert-sort (rest in-lon)))))) (define (insert value in-lon) <same definition for insert as in last example>)) (insert-sort in-lon)))
We could have also written it… (define (sort in-lon) (local ((define (insert-sort in-lon) (cond ((empty? in-lon) empty) (else (insert (first in-lon) (insert-sort (rest in-lon)))))) (define (insert value in-lon) <same definition for insert as in last example>)) (insert-sort in-lon))) Now we use two internal definitions to take over the entire functionality of the program
They are Functionally Equivalent
Want to Know More? READ INTERMEZZO III! Intermezzo III contains a large number of examples and detailed explanations of the particulars of local. It would be a good idea to read that material before attempting to use local on your own.
Similarities in Functions As we develop more and more functions, one of the things we start to notice (besides how sore our fingers become) is just how much many of our functions resemble each other. There’s always that “one little difference” that makes it a different function, however. So what if we could get rid of that one little difference?
A simple example (define (contains-doll? in-los) (cond ((empty? in-los) false) (else (cond ((symbol=? (first in-los) ‘doll) true) (else (contains-doll? (rest in-los))))))) (define (contains-car? in-los) (cond ((empty? in-los) false) (else (cond ((symbol=? (first in-los) ‘car) true) (else (contains-car? (rest in-los)))))))
A simple example (define (contains-doll? in-los) (cond ((empty? in-los) false) (else (cond ((symbol=? (first in-los) ‘doll) true) (else (contains-doll? (rest in-los))))))) (define (contains-car? in-los) (cond ((empty? in-los) false) (else (cond ((symbol=? (first in-los) ‘car) true) (else (contains-doll? (rest in-los))))))) WHERE’S THE DIFFERENCE?
A simple example (define (contains-doll? in-los) (cond ((empty? in-los) false) (else (cond ((symbol=? (first in-los) ‘doll) true) (else (contains-doll? (rest in-los))))))) (define (contains-car? in-los) (cond ((empty? in-los) false) (else (cond ((symbol=? (first in-los) ‘car) true) (else (contains-doll? (rest in-los)))))))
Getting rid of the difference (define (contains-symbol? in-los in-symbol) (cond ((empty? in-los) false) (else (cond ((symbol=? (first in-los) in-symbol) true) (else (contains-symbol? (rest in-los) in-symbol)))))) Here we make a generic searching function for a symbol in a list of symbols. To satisfy the problems that ask us to write contains-doll? and contains-car?, we can do the following:
Specific functions (define (contains-doll? in-los) (contains-symbol? in-los ‘doll)) (define (contains-car? in-los) (contains-symbol? in-los ‘car)) (define (contains-bat? in-los) (contains-symbol? in-los ‘bat)) (define (contains-ice-cream? in-los) (contains-symbol? in-los ‘ice-cream))
A new class of problems So that was pretty easy. All we had to do was add extra parameters and we created this “generic” searching function for lists of symbols. But what about lists of numbers? Or lists of posns? Or lists of TAs? If we examine the code for each…
(define (contains-num? in-lon in-num) (cond ((empty? in-lon) false) (else (cond ((= (first in-lon) in-num) true) (else (contains-num? (rest in-lon) in-num))))))
(define (contains-posn? in-lop in-posn) (cond ((empty? in-lop) false) (else (cond ((and (= (posn-x (first in-lop)) (posn-x in-posn)) (= (posn-y (first in-lop)) (posn-y in-posn))) true) (else (contains-posn? (rest in-lop) in-posn))))))
;; a TA has a name, hours worked and a salary (define (contains-TA? in-loTA in-TA) (cond ((empty? in-loTA) false) (else (cond ((and (symbol=? (TA-name (first in-loTA)) (TA-name in-TA)) (= (TA-hours (first in-loTA)) (TA-hours in-TA)) (= (TA-salary (first in-loTA)) (TA-salary in-TA))) false) (else (contains-TA? (rest in-loTA) in-TA))))))
These functions are almost identical! Upon inspection, the degree to which these function differ is tiny! For symbols we use (symbol=? …) to test to see if a specific symbol is in our list. For numbers, we use (= …). For structures, we make sure that every field in both structures are equivalent. Do we see this only when we search for values?
Write a function lower-than that takes in a number and a list of numbers and returns a list of numbers in which every value in our list is SMALLER than the inputted number. Write a function higher-than that takes in a number and a list of numbers and returns a list of numbers in which every value in our list is LARGER than the inputted number. How about these problem statements?
(define (lower-than in-lon in-num) (cond ((empty? in-lon) empty) (else (cond ((< (first in-lon) in-num) (cons (first in-lon) (lower-than (rest in-lon) in-num))) (else (lower-than (rest in-lon) in-num)))))) (define (higher-than in-lon in-num) (cond ((empty? in-lon) empty) (else (cond ((> (first in-lon) in-num) (cons (first in-lon) (higher-than (rest in-lon) in-num))) (else (higher-than (rest in-lon) in-num))))))
(define (lower-than in-lon in-num) (cond ((empty? in-lon) empty) (else (cond ((< (first in-lon) in-num) (cons (first in-lon) (lower-than (rest in-lon) in-num))) (else (lower-than (rest in-lon) in-num)))))) (define (higher-than in-lon in-num) (cond ((empty? in-lon) empty) (else (cond ((> (first in-lon) in-num) (cons (first in-lon) (higher-than (rest in-lon) in-num))) (else (higher-than (rest in-lon) in-num))))))
Wouldn’t it be neat… Wouldn’t it be nifty if we could some how pass FUNCTIONALITY as an argument just as easily as we pass values? We could just pass a specific “equality” test to each of our contains? functions, or a numerical comparison test to our higher-than and lower-than functions. We could create truly GENERIC functions that fit a common pattern and apply specific behavior based on our whimsy!
Surprise! Most modern programming languages (“Or at least those that call themselves useful” as an anonymous instructor put it) have the ability to do just that. We can pass, as a parameter, the body of our function to other functions and apply that body to a list of arguments. In many languages this is a bizarre and complex process involving function pointers and special “glue” functions that put our pieces together. In Scheme it just couldn’t be easier.
Everyone’s seen this: We all understand, at this point that Scheme will interpret “+” as an addition operation to be performed on 1 and 2.
And this… Now we’re passing the symbol “+” to the list function along with the numbers 1 and 2. List creates a list of those values for us.
But what about… What about something like: What’s going on here?
It gets even weirder! This is the body of the function my-func. If we look carefully at the function, we see that in the place of our usual call to the addition function, the subtraction function, the list function, etc., we have the parameter “operator” instead!
Scheme has a very simple set of rules. One of those rules is: Unless “quoted” out, treat anything that follows a left parenthesis as a call to a function.
So our definition states: This function takes in three parameters named operator, op1, and op2. Operator is really a function. We should apply the function “stored” in operator to the values stored in op1 and op2.
So we can do…
Pass the function “+” and the values 1 and 2 to my-func
So we can do… Pass the function “-” and the values 1 and 2 to my-func
So we can do… Pass the function “ * ” and the values 1 and 2 to my-func
Next Time… How to use this to generalize functions!