|
Comp210: Principles of Computing and Programming
|
set!
Consider:
What's going on? This "function" has some hidden state! Or, consider the phone company, adding new numbers:(random 10) (random 10) (random 10)
(lookup 'presley) = false (add-entry! 'presley 8675309) (lookup 'presley) = 8675309
In this latter case, presumably there is a placeholder directory
which is sitting around, and the list bound to directory
changes over time -- a variable. How can we get our
functions to do this? It could certainly be fudged by using set-struct-field!
,
with a bit of work; but it can also be done a bit more directly, with a new
piece of syntax:
(set! placeholder expr) set! mutates the placeholder to refer to the results of the expr. Example:
|
Changing a binding is the work of set!
.
(define speed-limit 75) (define scofflaw-margin 1.1) (define my-cruising-speed (* scofflaw-margin speed-limit)) my-cruising-speed = 82.5 (set! speed-limit 55) speed-limit = 55
What is my-cruising-speed?
82.5, not 60.5 because my-cruising-speed was calculated before speed-limit was set to a new value.
(If we'd really wanted my-cruising-speed
to depend on the
speed-limit
, we should make it a function.)
set!
is not set-struct-field!
Similarly, if you call(define a (+ 2 3)) (define b a) (set! a 'volkswagen) b ;; --> 5 unchanged! (define aa (make-posn 2 3)) (define bb aa) (set-posn-x! aa 99) bb ;; --> (make-posn 99 3) changed!
translate-posn!
on bb
, both
aa
and bb
can notice a change. But:
The only way for(define (silly x) (begin (set! x 17) ; legal in "Pretty Big" but not "Advanced" language level x)) aa ;; --> (make-posn 99 3) (silly aa) ;; --> 17 aa ;; --> (make-posn 99 3) bb ;; --> (make-posn 99 3)
aa
to get a new value is by
evaluating
(set! aa ...)
; similarly/separately for bb
.
(Note that the placeholders a
, b
don't get new values;
just, the structure they refer to is modified.)
This is called "pass-by-value". When calling a function, the arguments (values) are copied onto a piece of paper, and handed to the function; even if re-writing on those pieces of paper, the function can't re-write the caller's data (if it's not in scope).
Using set-first!
and set-rest!
, could you write
a function
What if items contained only target? (To really do this, you'd need a structure containing a list!) This is a huge problem--one that we will solve at the end of the semester!;; remove!: any, list --> (void) ;; Modify the list "items" so that ;; it no longer contains any occurrence of target. ;; (define (remove! target items) ...)
Another example:
A random-number generator:
Calling(define seed 0) ;; return a pseudo-random int in [0,100) (define (rand) (begin (set! seed (next-num seed)) seed)) ;; next-num: number --> number ;; Return the next number in an (ahem) random sequence. ;; (define (next-num n) (remainder (+ 17 (* n 41)) 100))
(rand)
repeatedly yields: This yields the sequence 17,
14, 91, 48, 85, ... Of course, the limit 100 could be made more flexible: How could we make it
so ? (define max-rand 100)
set! mutates what a placeholder references.
set-struct-field! mutates the value of a field inside of a given instance of a structure.
;; A directory is a list of entries, initialized to empty (define directory empty) ;; And entry is a struct with a name ;; and a number. ;;(make-struct sym number) (define-struct entry (name number)) ;; add-entry!: symbol number --> list-of-entries ;; SIDE-EFFECT: add a new entry to directory. ;; We return (the updated) directory. ;; (Not necessary to return this, since anybody can ;; look up the placeholder "directory" at any time, ;; to get that same info.) ;; ;; However, a useful rule of thumb: when writing a function ;; which modifies an object but doesn't otherwise return ;; an interesting value, have it return the object being modified. ;; This is often convenient in allowing you to pass that ;; value to the next function, w/o having to use "begin". ;; This is called "chaining". ;; (define (add-entry! name number) (begin (set! directory (cons (make-entry name number) directory)) directory)) ;; lookup: symbol --> entry or false ;; (define (lookup name) (lookup-helper name directory)) ;; lookup-helper: symbol, list-of-entries --> entry or false ;; (define (lookup-helper name entries) (cond [(empty? entries) false] [(cons? entries) (if (symbol=? (entry-name (first entries)) name) (first entries) (lookup-helper name (rest entries)))]))
There is one big problem with this: the directory is public to everybody,
and other programmers might (inadvertently) change the directory directly.
And perhaps incorrectly, even if they're smart people: maybe your
add-entry!
prohibits duplicate entries, and your lookup
relies on this
fact, but somebody else didn't realize this and bypassed your
add-entry!
!
The above code can be downloaded here: lect32.scm
Example: You are disgruntled, about to quit, and you write code that
will stop working after 100 times (just returning 'haha instead of answer).
(define times-called 0) (define select-by-color (lambda (kroma) (begin (set! times-called (add1 times-called)) (if (> times-called 100) 'haha ...the real code...))))
Use local and factories
We don't want the placeholder to be global. Move it inside the define, before the lambda.
Question: does this make a new copy of the placeholder every time select-by-color is called? Or just one placeholder? (Clearly, we want the latter.)
To answer, remember the law-of-scheme, the semantics of "define". In particular, consider the following example:
(define x (+ 3 4))
Is + called every time x is used, or just once?
Answer: The way define
works, just once. Here, "local"
is analagous to "+". It is
only evaluated once, creates a placeholder with a name inaccessible anywhere
else (say, times-called%473
), and then it returns a value (a
function which includes times-called%473
in its closure).
What if we'd put the local
inside the lambda? Then every time we call the function, the
local
is evaluated, making a new placeholder every single time, and each function
has a different times-called%xxx
in its closure. For
instance:.
(define function-x (lambda () (+ 3 4)))
Now, every time function-x
is called, the + is evaluted.
Factories can help us create the locals we need, both explictly by declaring local definitions in the appropriate places or implicitly by the local definitions created automatically for input parameters.
The closures created by locals and factories hide the placeholders being mutated, minimizing their damaging effects to other parts of the program.
Example: Counters, both inter-dependent and independent.
To make counters that are independent, the factory creates counters where the local is defined for each counter made:
;;countFac: --> (lambda: --> num) ;; factory to that returns a counter function, ;; which increments and returns the new value ;; of a counter. The counter is initialized to ;; zero. (define (countFac) (local [(define counter 0)] (lambda () (begin (set! counter (add1 counter)) counter))))
To make counters that are inter-dependent, we simply put the local outside of the factory itself. Notice that we had to use the other ("real" -- for us old fogies) way to define a function:
;;countFac: --> (lambda: --> num) ;; factory to that returns a counter function, ;; which increments and returns the new value ;; of a counter. The counter is initialized to ;; zero. (define countFac (local [(define counter 0)] (lambda () (lambda () (begin (set! counter (add1 counter)) counter)))))
Structures holding lambda create "objects" with multiple capabilities. Factories and locals are used to create cohesive, yet encapsulated behavior of those lambdas:
;; Counter structure that represents a counter ;; tied to a total count. ;; inc adds one to the total count and returns it. ;; reset resets the total count to zero. ;; total returns the total count ;;(make-Counter (inc: --> num) (reset: --> void)) (define-struct Counter (inc total reset)) ;;depend-countFac --> Counter ;; Factory for a Counter struct ;; All counters made have the same total count (define depend-countFac (local [(define total 0)] (lambda () (make-Counter (lambda () (begin (set! total (add1 total)) total)) (lambda () total) (lambda () (set! total 0)))))) ;;indep-countFac --> Counter ;; Factory for a Counter struct ;; All counters made have their own total count (define indep-countFac (lambda () ;; could have written (define (indep-countFac) ...) (local [(define total 0)] (make-Counter (lambda () (begin (set! total (add1 total)) total)) (lambda () total) (lambda () (set! total 0))))))
This technique of using a structure to hold lambdas is very important and we will be using it frequently from now on.
The demo code can be downloaded here: counter.scm Be sure you understand the why to incorrect code doesn't work!
When is an example when we might want indeed to have the local be inside the lambda, to create a local var many times? Probably when the value returned is a function and each function returned wants to use its own local variable (alternative: all functions returned refer to the same variable.)
What is wrong with the following?
;; make-shopper-card: name --> (command, any --> any) ;; The is a factory for shopping cards (define make-shopper-card (local [(define most-recently-bought #f)] (lambda (name) (lambda (command some-item) (cond [(eq? command 'show-name) name] [(eq? command 'buy) (set! most-recently-bought some-item)] [(eq? command 'show-fave) most-recently-bought])))))
The local is in the wrong place. It needs to be moved inside the make-shopper-card factory but outside actual shopper card lambda.
;; make-shopper-card2: name --> (command, any --> any) ;; (define (make-shopper-card2 name) (local [(define most-recently-bought false)] (lambda (command some-item) (cond [(eq? command 'show-name) name] [(eq? command 'buy) (set! most-recently-bought some-item)] [(eq? command 'show-fave) most-recently-bought])))) "make-shopper-card2 tests:" (define sw2 (make-shopper-card2 "sw")) (define jg2 (make-shopper-card2 "jg")) (sw2 'show-name null) (jg2 'show-name null) (sw2 'show-fave null) (jg2 'show-fave null) (sw2 'buy "pizza") (sw2 'show-fave null) (jg2 'show-fave null) ;; fixed! ;;--- Alternative implementation using struct and lambdas ----------------- ;; A shopping card is a structure with that ;; holds three functions (lambdas): ;; getName returns the name associated with the card ;; buy will set the most recently bought item to the given string ;; getFave will return the most currently bought item ;; (make-shopcard ;; (lambda: --> string) ;; (lambda: string --> void) ;; (lambda: --> string)) (define-struct ShopCard (getName buy getFave)) ;; shopcardFac: string --> ShopCard ;; Factory for ShopCard that takes the ;; name string associated with the card ;; as an input. (define (shopcardFac name) (local [(define most-recently-bought false)] (make-ShopCard (lambda () name) (lambda (item) (set! most-recently-bought item)) (lambda () most-recently-bought)))) "ShopCard tests:" (define sw3 (shopcardFac "sw")) (define jg3 (shopcardFac "jg")) ((ShopCard-getName sw3)) ;; remember that getName returns a function,so it needs to be evaluated. ((ShopCard-getName jg3)) ((ShopCard-getFave sw3)) ((ShopCard-getFave jg3)) ((ShopCard-buy sw3) "pizza") ((ShopCard-getFave sw3)) ((ShopCard-getFave jg3))
Notice how the alternative implementation eliminates the
cond
statement in favor of direct dispatching to the desired behavior.
The shopping card code can be found here: shopcard.scm
Sometimes, to get the various levels of scoping we need but yet still maintain safe encapsulation, we need factories that make factories.
Example: We want to make groups of students, where each group belongs to a common school.
Solution: We need a factory that takes a school name and returns to us another factory that makes students that belong to that school:
;;studentFacFac: string -->(lambda: string --> (lambda: --> string)) ;; A factory that takes in a school name and ;; returns a factory that takes in a student name and ;; make students (lambdas) that return the phrase ;; "My name is [name] and I go to [school]" (define (studentFacFac school) (lambda (name) (lambda () (string-append "My name is " name " and I go to " school "."))))
Notice how layers of factories create layers of encapsulations.
The demo code can be downloaded here: studentfacfac.scm
In Scheme, the fundamental unit of computation is function-call. (A
functional language)
In imperative langauges (Java, C, assembly), the fundamental unit is
assign-to-variable (set!
) and sequencing (begin
),
with a lesser emphasis on calling functions. (This functional/imperative
classification is independent of whether or not a language is
object-oriented.)
set!
(These following uses are not always clean-cut; e.g. keeping track of history is one common example of keeping state.)
random
) When using set!
, you might want to reflect on which of these
purposes you're using it for. And if not any of these, you might want to
reflect on whether your program needs set!
, and if the
structure of the code might better match the structure of the data if you
use one of the templates (structure, w/ accumulator, generative).
Don't forget everything you've learned previously!
Last Revised Tuesday, 24-Aug-2004 13:49:20 CDT
©2004 Stephen Wong and Dung Nguyen