Comp210 lectures 98.fall, ian barland lect 0: 98.aug.31 (mon) - Welcome. This class is an intro to programming, emphasizing well-built programs, data format, problem-solving skills. We happen to use Scheme. - CS is a slave to other disciplines. If you just want to program, comp210,comp212,books,practice. (comp200, comp100-intro to computing&info systems) If you want to go c.s., comp320, comp280, ... - syllabus, books, lab sections - First part of course: high-level; then low-level - read intro,chpt1 - hw on web; - What is computing? The How of Functions. lect 1: still 98.aug.31 (mon) - arithmetic expressions arith | scheme | value ------------+------------------+------ 4+7 | (+ 4 7) = 11 2*3 | (* 2 3) = 6 (4+7)*2 | (* (+ 4 7) 2) = 22 4+(7*2) | (+ 4 (* 7 2)) = 18 4 | 4 = 4 pi=3.14 | (define pi 3.14) = ??? (not quite an expression -- what value?) pi*17^2 | (* pi 17 17) = #i907.46 lect 2: 98.sep.02 (wed) [Questions about first lab; book. Remind: take owlnet courses!] Re-cap expressions in arith,scheme; values 4+(7*2) | = 4 | = let pi=3.14 | (define pi 3.14) = ??? (nothing: define) pi*17^2 | = 907.46 let x=17 | (define x 17) = ??? (nothing: define) pi*x*x | (* pi x x) = 907.46 A(r) = pi*r^2 | (define disc-area (lambda (r) (* pi r r))) = ??? (nothing: define) A(7) | (disc-area 7) = 907.46 Note: parentheses in scheme have *exactly one* meaning: apply a function. (+ 2 3), (* 4 (+ 2 3)), (disc-area (+ 2 5)) all applying functions. ;; Only mention later, ian: Exceptions to the paren-means-apply rule: ;; (Well, okay, there are about four exceptions in all of scheme, ;; and we've already seen two: mainly, the argument to lambda. ;; And technically "define" and "lambda" aren't fucntions, though you ;; can kinda think of them that way.) Now, define "washer-area". Note, on how we've re-used values (via place-holders): New scientific breakthrough: pi is not exactly 3.14; it's exactly pi=3.14159. Or, area of a circle changes (the disc-stamping machine leaves a rilled edge) (should probably change the name of the function in this case) On the side: Expressions are: 1. values (numbers, ...) 2. placeholders 3. function application (exp0 exp1 exp2 ... expn) a. primitive b. constructed 4. Special forms: define; lambda, if, cond NB Functions are different than function-applied-to-a-particular-inputs. ;; weight-on-planet: num,num --> num ;; ew: a person's weight on earth, in Newtons. ;; pm: a planet's mass, in earth-masses. ;; pr: the planet's radius, in earth-radii. ;; Return the person's weight (in Newtons) on another planet, ;; wearing a space-suit. ;; (define weight-on-planet (lambda (ew pm pr) (/ (* (+ ew 50) pm) (* pr pr)))) ; the space-suit weights 50lbs. ----reached here---- lect 3: 98.sep.04 (fri) [Remind: i suggest you take owlnet unix-literacy courses!] (weight-on-planet 150 0.10745 0.5326) = #i75.76... Wait, we should have ... = #141.3 ... Uh-oh, a bug?! To convert to newtons, multiply by ... Better, replace 50 with "(lbs->newtons 50)". Note: we haven't written lbs->newtons. But: Presuming that subsidiary call returns the right thing, then we're sure the overall function works. (a minor leap of faith) [This is a lead-in to the leap-of-faith of recursive functions, later.] ; NB A newton is a kg*m/s/s. ; g = 9.817m/s/s = 32.174ft/s/s ; 2.2lb = 1kg @ 9.817m/s/s ; 1 lb = 4.462 netwons (define lbs->newtons (lambda (lbs) (* (/ 9.817 2.2) lbs))) ; g/(lbsPerKilo) Program development steps: 1. contract 2. examples 3. header 4. body 5. test Okay, boolean values: 3 >? 2 | (> 3 2) = #t 3 >=? 4 | (> 3 4) = #f 7^2 + 24^2 =? 25^2 (and (> 3 2) (= 4 (+ 2 2)) (number? 4)) step(x) = { if x>=0, 1 { else 0 symbols: they mean nothing, except suggesting concepts in people's minds. All scheme can do with them: ask "eq?" cond: (define planet-mass (lambda (p-name) (cond [(eq? p-name 'mercury) 3.303e23 ] [(eq? p-name 'venus) 48.69e23 ] [(eq? p-name 'earth) 59.76e23 ] [(eq? p-name 'moon) 0.7349e23] [(eq? p-name 'mars) 6.421e23 ] [(eq? p-name 'jupiter) 19000e23 ] [(eq? p-name 'uranus) 5688e23 ] [(eq? p-name 'saturn) 868.6e23 ] [(eq? p-name 'neptune) 1024e23 ] [(eq? p-name 'pluto) 0.129e23 ]))) ; [else (error 'planet-mass "unknown planet: ~s" p-name)]))) Note on "else": In drScheme, at beginner level, it is an error if nothing matches. At higher language levels, you should raise an error with an "else" clause, or have your program explicitly return some value like #f. (persons-weight-on (lbs->newtons 150) 'mars) = (weight-on-planet 150 (/ (planet-mass p) (planet-mass 'earth))) Really, we want a way to bundle together a planet name and its mass, along with whether or not scientists feel it could sustain a fast-food franchise. We want a list with three things in it. It turns out, scheme has a way to tack things on to an existing list. Of course, we need one existing list to get started: empty. We create new lists from existing ones with "cons". Here's a list with three things in it: (cons 'earth (cons 59.76e23 (cons #t empty))) How to get things out of it? first,rest (first (cons 3 empty)) = 3 (rest (cons 3 empty)) = empty (first (cons 'earth (const 59.76e23 (cons #t empty)))) = 'earth (rest (cons 'earth (const 59.76e23 (cons #t empty)))) = (const 59e23 (cons #t empty)) How can we access the second element of this list? NOTE: Lists are values! empty -- a new constant cons: value,list --> list first: list* --> value rest: list* --> list *Well, a non-empty list. Cons always takes exactly two arguments; first/rest are its inverse. Now that we're bundling planet info together, let's formalize to ourselves that we have a new *type* of data: A planet is (cons symbol (cons num (cons bool empty))) We've already seen an example of a planet. Let's write a selector: ;; planet-franchise? : planet -> bool ... Using this selector is preferable to calling first-rest-rest all over the place. ------------ 98.sep.11 -------- day skipped due to a bit of rain! Optional: (+ 3 (if ...)) (sqrt -1) ------------ 98.sep.14 -------- Okay, we hit you with two unrelated items: - Lists to bundle data. (They are values!) empty,const;first,rest. - Data definition. In comments. What about lists with arbitrary number of items? Okay. We need a data defn. What's wrong with "a sequence of items"? "The 1st,2nd,3rd,...,nth item". Okay, try: A ListOfSymbols is - empty - (cons symbol ListOfSymbols) This is our first interesting data definition! Examples: We'll write the function length: (length empty) = 0 (length (cons 'amy empty)) = 1 (length (cons 'bob (cons 'amy empty))) = 2 [In one fell swoop, before they see template, write length] What is length of the above sample data? It takes a while for the process to sink in; see lab/donkey. When writing the functions, don't think too much :-) Now write all-mike? contains-mary? count-marys. What looks the same? Okay, now we'll write other programs dealing with list-of-symbols. Example: contains-mike Hey, what is the general pattern we're following? The Design Recipe:
  1. Write any pertinent data definitions (e.g., a List-Of-Numbers).
  2. Write sample inputs meeting that data definition.
  3. Derive a template for a generic function (e.g., handle-lon).
    1. Case Analysis
    2. Data Extraction
    3. Natural Recursion
  4. Give the contract (the types of inputs and outputs), and description (as needed).
  5. Sample test cases (presumably based on your sample inputs).
  6. function header
  7. Fill in the template to get a working program.
  8. Test your program on the sample inputs.
Do all of these steps inside DrScheme comments, except #6 and #7. ----------- 98.sep.16 ------------ Common problems w/ homework 1: Set language level! (and quit normally, not just shutting-down) Magic number of 80mph Re-use "dist" "if" on 1 line or on 3, never 2. [Question: For hw, is empty a positive number?] More examples: product [some disbelief that (product empty) is sensical] convert-temps: List-of-Numbers -> List-of-Numbers subst-mike ;; He changes his name to mikey ----------- 98.sep.18 (fri) ------------ Note: sure, you can use define to save typing: (define fahr-temps (cons 212 (cons 32 (cons -40 (cons 451 empty))))) (convertTemps fahr-temps) = ... (Of course, this doesn't change the define'd value of fahr-temps, no more than (sqrt pi) or (* 7 7) change the value of pi or 7.) Recall the data defn Planet: A Planet is (cons (cons (cons empty))) We wrote planet-franchisable?, planet-name, ... These are called selectors. We could even write a constructor: (define (make-planet name mass francs)) Advantages to this abstraction? The usual two: - embed your meaning into code - adding fields later necessitates only small changes Def'n of a Planet-List: ... [ Give entire examples! People confused by list-of-lists; thinks "first" returns the first flattened element. ] Write: select-franchisable (this is just like hw prob... length, also, is identical) half-profitable?: are half the planets in a list franchisable? Example: (define add3 (lambda (x) (+ x 3))) The Law of Scheme: 1. values (numbers, bools, symbols, lambda expressions, lists) 2. placeholders 3. function application (exp0 exp1 exp2 ... expn) First, resolve to (val0 val1 val2 ... valn) (NOTE: recursion!) The first one had better be a function! a. prim b. lambda 4. Special forms: define, (if, cond...why are these special?) Note that open-paren *always* means "apply a function" (...except for the special-forms, like cond, define, lambda) ----- Re-cap: examples of Planet-List How to implement planet? define-struct posn planet inventory-record (this is in intermezzo-1) Law-o-scheme: why is "if", "cond", "and" special? [I only got there, since i went along "here are the functions we use;... how would you write 'planet?'?" [Answer: using tags.] There is a built-in way...better since the language construct lets you reflect your intent: this is a form of documentation.] [ Before here, i should have covered insert-sort! ] Data defn: non-empty lists. max. (how would you write biggest-shape ?) ----- 98.sep.25 (fri) Review: write (sum-of-cubes n). When filling in template, be concrete in the example. Examples of structures. More complicated data defns. Data defn w/ > 2 cases: Shapes: A Shape is - make-circle posn rad - make-rectangle nwCorner width height - make-line strt fin N.B. draw-solid-{rect,line,disk} Write "area: shape -> number" [Also, perhaps and example of a structure with two shallow fields and one self-referential field??] Family-tree: Data Def'ns with two recursions: - family tree picture To be continued... ---- 98.sep.28 (mon) [spring: 98.feb.06] (how'ld you Write "area-of-all: list-of-shape -> number" "max-area: nelist-of-shape -> shape" "save-paint: lsit-of-shape -> list-of-shape" Family-tree: Data Def'ns with two recursions: - family tree picture - family tree data defn - family tree template - functions: size, is-descendent-of Note: these two functions *include* the person as their own ancestor Use overheads, overlapped to emphasize template re-use. We've seen how family-tree functions are easy, if you follow template. It suggests for you, that the answer for a person is combined from the answers for mother, father (and self). [put up overhead of descended-from-Betty?] Now, another question: (hint: template will guide you!) We've seen how family-tree functions are easy, if you follow template. It suggests for you, that the answer for a person is combined from the answers for mother, father (and self). [put up overhead of descended-from-Betty?] Now, another question: (hint: template will guide you!) ;; path-to-Betty : FamTree --> list-of-symbols or #f ;; Returns #f if there is no path to 'betty ;; Otherwise returns a list of ancestor's names, ending in 'betty ;; Ex: if 'fred's maternal grandmother is 'betty, then ;; (path-to-betty fredsFamTree) = (list 'fred 'amy 'betty) ;; (define path-to-Betty (lambda (aft) (cond [ (symbol? aft) #f ] [ (child? aft) (cond [(eq? 'betty (child-name aft)) (list (child-name aft)) ] [(cons? (path-to-Betty (child-ma aft))) (cons (child-name aft) (path-to-Betty (child-ma aft)))] [(cons? (path-to-Betty (child-pa aft))) (cons (child-name aft) (path-to-Betty (child-pa aft)))] [else #f]) ]))) (Didn't quite finish this...) ----- Rules of _scope_: - Note that (lambda (x) (+ x 3)) and (lambda (y) (+ y 3)) are the same function; the name is a local concern. What about... (define x 7) (define add3 (lambda (x) (+ x 3)) (add3 5) = ?? (add3 x) = ?? x = ?? ---- 98.feb.04 Data Def'ns with two recursions: - family tree picture - family tree data defn - family tree template - functions: size, is-descendent-of Note: these two functions *include* the person as their own ancestor Use overheads, overlapped to emphasize template re-use ---- ---- 98.sep.30 (wed) Hw Question: how would you write reverse? Answer: what does template give you? By popular demand, we wrote add-at-end in class. You can now use "list", in addition to "cons"! That was chpt 16. Here is chpt 18 (called 17 in handout). Okay, let's look at trees going in the other direction: (define-struct parent (kids eyes birth)) where kids is a list-of-children is - null - (cons p loc) where p is parent, loc is list-of-children [picture], examples. Write templates. Write size. ---- 98.oct.02 ?? Announce: test on ... Note on FamTree defn: what might be more appropriate than using 'unknown to represent an empty FamTree? Now, write ancestor-of-adam. (What would the analog of path-to-betty be?) Review: write insert Arguments of multiple complex types: Shape - interesect (the template only). Two lists: interleave. Two natNums: >, only using recursion, if, zero?, positive?. append, nth-car, nth-cdr ---- 98.oct.05?? [Test will be a week from friday/weekend] accumulator: ;; bounced-a-check?: lon --> bool ;; given a list of bank transaction and an initial bank balance, ;; tell whether a check might bounce. ;; (A positive transaction means a deposit.) ;; (define bounced-a-check (lambda (transactions) (if (null? transactions) (negative? bal) (bounced-a-check (+ bal (first transcation)) (rest transaction))))) ; Why doesn't this work? This only looks a the final balance. ; Add (or (negative? bal) ...) ; (But then this doesn't seem tail-esque.... add a clause ; that if balance > 100000, then doesn't count as bounced???) ;; Solution: ;; (define bounced? (lambda (bal transacts) (cond [(empty? transacts) (negative? bal)] [else (or (negative? bal) ;;(negative? (+ bal (first transacts)) (bounced? (+ bal (first transacts)) (rest transacts)))]))) ;; Note the repeated code: ;; In this case it stems from having to check the initial balance. ;; A fix(?): empty case returns #f; we check for (negative? (+ bal ...)) ;; 'Course then, we have the + replicated. We write nth-rest, together. Note how we sum l-to-r. Write "product-accum". ;; product-accum: num, lon --> num ;; Return the product of acc and the numbers in lon. // Include this descrip! ;; ... --------- 98.oct.07 Finish writing product-accum; Hand-eval. Compare the hand-eval with that of plain ol' product: what is the difference? No pending computations build up. The accum version is "tail recursive": the answer to the compuation is precisely the answer to the recursive call. (We'll see why we bother to give that a name, later.) Another example: ;; index-of#2: sym, list-of-symbol --> num or #f ;; return the position of target in list (or #f if not in list), ;; assuming we start counting at "index-of-first". ;; ;; Example: (index-of#2-helper 'ian (list 'johann 'ian 'ivan) 4) = 5 ;; (define index2-help (lambda (target data index-of-first) (cond [(empty? data) #f] [else (if (eq? target (first data)) index-of-first (index2-help target (rest data) (add1 index-of-first)))]))) (define index-of#2 (lambda (target los) (index2-help target los 1))) -------- 98.oct.09 [John G] Any questions on accumulators? [If so, you can write reverse, accumulator style. We have previously discussed a non-accumulator approach.] Test: 3hrs allowed, should probably take you 1.5hrs, closed computer, Minor clarification: on problem 3c, it will say "write a template for XX and YY". This means "write a template for XX, and a template for YY". The problems are all things you've seen in hw and/or lab. Nothing is meant to be a trick question. "It's easy!" It will cover up to wednesday's lectures (a slight familiarity with accumulators.) Title of lecture: Avoiding Idiotic Re-computation In many previous examples, we've called the same function twice: [put up slide of path-to-betty] These repetitions seems a bit ugly, because (1) it violates the idea of single-point-of-control (If something changes, updates should be needed in only one place.) (2) it's a pain to type twice, and read twice, (3) scheme will compute it twice, which is idiotic. (Functions always return the same value, given the same input.) In fact, this point (3) might be worse than you think: Looking at the slide, (define path-to-betty (lambda (aft) (cond [(eq? 'unknown aft) null] [else (cond [(eq? 'betty (child-name aft)) (list (child-name aft)] [(cons? (path-to-betty (child-ma aft))) (cons 'mothers (path-to-betty (child-ma aft)))] [ ...same with father... ] [else null])]))) Note the repetition (let's consider just the maternal side): we check the mother's side twice. But each of these two checks looks at the grandmother twice: so that's four times. But each of those four checks of grandma checks great-grandma twice: that's eight times. my nth-mother is checked 2^n times! (same with father) We unveil a solution to this ungainly problem: "local". First, an example: [else (cond...)] becomes [else (local [(define mothers-side (path-to-betty (child-ma aft))) (define fathers-side (path-to-betty (child-pa aft)))] (cond [(eq? 'betty (child-name aft)) (list (child-name aft))] [(cons? mothers-side) (cons 'mothers mothers-side)] [ ...same with father... ] [else #f]))] Things to note: (1) Most importanltly, the maternal & paternal sides are computed only once. (2) The new placeholders are known only inside the local. (3) We call (child-name aft), but didn't condense it with local. Discussion: Is this bad? A matter of taste -- this function is quick so calling it twice is unnoticable. And it's so transparent, giving it a name with local is almost in the way. But it does achieve single point of control. Upshot: in this situation, it's a matter of taste. Pragmatic#2: give names to individual parts of functions But for computing the mothers/father's sides, it's UNFORGIVABLE to compute the same thing twice, now that you know about local. Syntax of local: (local [(define ph1 exp1) ... (define phn expn)] body) Recall max: [write max on the board, with space to later modify it:] ;; max: non-empty-list-of-number --> number ;; (define max (lambda (lon) (cond [(empty? (rest lon)) (first lon)] [else (if (> (first lon) (max (rest lon))) (first lon) (max (rest lon))))]))) It turns out, this repetition has the same exponentional explosion: Finding the max of a list of 11 items, might mean computing max of a 10-item list is called twice. But this implies: Calling the max of a length-12 list means computing a length-11 list twice which means computing a length-10 list 4x. Calling the max of a length-13 list means computing a length-12 list twice which means computing a length-10 list 8x. Length 20 might repeat the computing max of a 10-element list 1024 times; Length 30 could repeat the same computation be a million times, Yowsa! Field trip to do on your own: Go to lab, and compute max of various-sized lists. How big do you need to get, before the delay's noticeable? [answer: 15] Now, turn to your neighbor, and use local to fix max: (define max (lambda (lon) (cond [(empty? (rest lon)) (first lon)] [else (local [(define max-of-rest (max (rest lon)))] (if (> (first lon) max-of-rest) (first lon) max-of-rest))]))) Semantics of local (Law of Scheme): ...top-level definitions.. (local [defs] body) becomes: ...top-level definitions.. ...defs... (renamed for uniqueness) body (with renaming) Pragmatics of local: (1) To avoid re-computation! ... seconardy reasons, to be mentioned. Another pragmatic use of local: index-of#2 used a helper function. Really, you want people calling your index-of, but you don't want them calling that helper function (they might get the extra argument wrong). How can you use local, to hide the helper function from others? You have 30sec. to sketch the approach! (define index-of#3 (local [(define helper (lambda (target los index-so-far) (cond [(null? los) #f] [(eq? target (car los)) (add1 index-so-far)] [else (helper target (cdr los) (add1 index-so-far))])))] (lambda (a-target a-los) (helper a-target a-los 0)))) [end friday about here??] ------- 98.oct.12 (mon) (greiner) But notice how the arg "target" to helper never changes; it might as well be top-level defined. Can we modify so that we don't repeatedly pass it along? ;; An accumulator version: ;; (define index-of#4 (lambda (a-target a-los) (local [(define helper (lambda (los index-so-far) (cond [(null? los) #f] [(eq? a-target (car los)) (add1 index-so-far)] [else (helper (cdr los) (add1 index-so-far))])))] (helper a-target a-los 0)))) Pragmatic#4: factor out unchanging arguments. Notice that to do this, we need to have the local *inside* the lambda, since obviously a-target and a-los are only known inside the lambda. Notice that there is a strange difference between index-of#3 and #4: one "local" is inside the lambda, the other isn't. Pragmatics of local: (1) ** To avoid re-computation! ** (2) Give names to individual parts of a computation. (3) Want to hide the name of helper functions. (4) Can avoid re-mentioning the unchanging argument. Scope: Of course, (lambda (x) (+ x 3)) and (lambda (y) (+ y 3)) Are clearly the same function. The name of the argument matters *only* inside the function. (lambda (y) (* y 17)) Of course, these two different y's are unrelated (since they're in different functions). This implies that names can be repeated among different functions, as you've almost certainly noticed: we *could* have called the argument to index-of#4 "target" instead of "a-target". Of course, it's a When multiple placeholders with same name, ... Each placeholder has a binding occurence (where it gets a value), and a scope (where it knows that value). Scope of a lambda-argument: the function body. Scope of top-level define: everywhere. Scope of local define: the local body (achieved by re-naming). Scopes have holes in them! (define z 17) (local [(define z 118) (define confusion (lambda (y z) (+ y z)))] (confusion z 5)))) Now wrap the above local in a lambda(z); evaluate it at 9. [greiner covered the last two lectures and got here with 20min to spare.] ---- 98.oct.14 feb.20 (Hi; back from wedding, but didn't take pix to finish roll of film.) Hi & Lois What natural number am i thinking of, in the range [0,1000)? Higher, Higher, Lower, yes! Let's write a program. What info did you keep track of? (what did you need to make the next guess?) ;; guess: num --> {'higher, 'lower, 'yes} ;; Given a number, tells if the hidden number is higher lower or equal. ;; ; [WRITTEN FOR YOU] [This is what i did up in front.] ;; hi-lo: num, num --> num ;; Given a low & high bound, return the hidden number (in [lo,hi]). ;; (This calls the function "guess" many times.) ;; (define hi-lo (lambda (lo hi) (local [(mid (truncate (/ (+ lo hi) 2))) (answer (guess mid))] (cond [(eq? answer 'yes) mid] [(eq? answer 'higher) (hi-lo mid hi)] [(eq? answer 'lower) (hi-lo lo mid)] [else (error 'hi-lo "Unknown answer ~s." answer)])))) Exercise: have it return the # of guesses made! (did you use accum? Don't need to!) Whoah! THis doesn't look like a template! It's not -- it's *generative* recursion. Structure ("natural") recursions: - recur on substructures of your input; - guaranteed to halt, says ian. Generative recursion: - recur on arguments which you generate yourself. - not guaranteed to halt. Let's try out the above; presuming the guess is 562..or, 563. (hi-lo 0 1000) = (hi-lo 500 999) ; diff is 500 = (hi-lo 500 750) ; diff is 250 = (hi-lo 500 625) ; diff is 125 = (hi-lo 562 625) ; diff is 63 = (hi-lo 562 593) ; diff is 31 = (hi-lo 562 577) ; diff is 15 = (hi-lo 562 569) ; diff is 7 = (hi-lo 562 565) ; diff is 3 = (hi-lo 562 563) ; diff is 1 = (hi-lo 562 563) ; diff is 1 = ... Yikes! ... termination not guaranteed; bugs not obvious. How to patch? - ask for both endpoints ... if diff is 1 - use half-open intervals, for integer ranges: [0,1000) Another example of generative recursion: ;; count: num,num -> list of num ;; Make a list of numbers lo...hi-1. ; Examples: (count 2 5) = (list 2 3 4) (count 2 3) = (list 2) (count 2 2) = empty (count 2 0) = empty (write with the help of a friend) Design recipe is not entirely tossed: - Data Def'ns - examples of data - template for those data - function contract - function header - function body: template ... if natural recursion generative: Handle terminating cases first; Think about what recursive cases handle work for you. - Test the function ---- oct.16 feb.23 generative recursion: paths in a graph (DFS) picture of graph; directed graph; edge; node (houston, chicago, la, slc, dc, nyc) (define-struct city (name nbhrs)) (define big-apple ('nyc (list 'dc 'chicago))) ... (define all-cities (list big-apple ...)) ;; name->city: symbol --> city ; You must yourself write a function which, given a city-name, ; can look up the city. ;; The problem is the same as miss&can, but this is a DIFFERENT approach. ;; (What are cities? Edges?) ;; path-from: city-name, city-name --> list-of-city-name-or-#f ;; (define (path-from src dest) (cond [(eq? src dest) (list src)] [else (local [(define subpath (path-from-any (city-nbhrs (name->city src)) dest))] (if (list? subpath) (cons src subpath) #f)))) ;; path-from-any: list-of-symbols, city-name --> list-of-city-name-or-#f ;; (define (path-from-any src-names dest ) (cond [(empty? src-names) #f] [else (local [(define path-from-first (path-from (first src-names) dest ))] (if (list? path-from-first) path-from-first (path-from-any (rest src-names) dest)))])) ... --------- ;; path-from: city-name, city-name --> list-of-city-name-or-#f ;; (define (path-from src dest already-seen) (cond [(eq? src dest) (list src)] [(contains? already-seen src) #f] [else (local [(define subpath (path-from-any (city-nbhrs (name->city src)) dest))] (if (list? subpath) (cons src subpath) #f)))) ;; path-from-any: list-of-symbols, city-name --> list-of-city-name-or-#f ;; (define (path-from-any src-names dest already-seen) (cond [(empty? src-names) #f] [else (local [(define path-from-first (path-from (first src-names) dest (cons (first src-names already-seen))))] (if (list? path-from-first) path-from-first (path-from-any (rest src-names) dest)))])) --------- But what about cycles?! Add an accumulator: visited. When recurring, add a check for already-visited. [ There are two optimizations here. First, when you recur, ...? Second note that any searching done by recursive calls is forgotten. If we could "mark" visited cities, that'd be fine. Short of that, we could have the functions return not only the answer, but a list of failed-cities searched. ] Add an accumulator "already-seen". ;; path-from: city, city --> union(list-of-city, #f) (define (path-from src dest already-seen) (cond [(eq? (city-name src) (city-name dest)) (list src)] [(contains? src already-seen) #f] [else (local [(define subpath (path-from-any (city-nbhrs src) dest (cons src already-seen)))] (if (cons? subpath) (cons src subpath) #f)))) (define (path-from-any src-name-list dest already-seen) (cond [(null? src-name-list) #f] [else (local [(define aWay (path-from (lookup-city (first src-name-list)) dest already-seen))] (if (cons? apath) aWay (path-from-any (rest src-name-list) dest already-seen)))])) This contained a tragic amount of repeated computation: you'd calculate (path-from houston slc (...)), and conclude there was no path to slc via nyc. This recursive call would return #f, and you'd forget that there was no path from nyc to slc, and might later try that again. Such repeated-tries might yield results for other problems, but clearly not here. What can we do? ---- oct. 21 (oct. 19 is break) feb.25 %Announcments: test back next time; how many copies of notes? In class, you saw a generative-recursive version of mergesort: (define (mergesort lon) (if (length-zero-or-one? lon) lon (merge (mergesort (first-half lon)) (mergesort (first-half lon))))) "Divide and conquer" To think about: why is this equivalent to the hw version? Here is *yet another* way of sorting numbers. It's comp212 where we really discuss these and look at their trade-offs. (define (qsort nums) (cond [(null? nums) null] [else (append (qsort (collect-lessers (first nums) nums) (collect-equals (first nums) nums) (qsort (collect-greaters (first nums) nums))))])) This calls three functions -- collect-{>,=,<} which each traverse the list. Exercise: write partition, which takes a list and a number, and returns a lsit-of-three-lists. How would you call that function only once, instead of three times? [If I write this on the board, then the above qsort could have used local in the first place.] (collect ; Could write a function ; ;;partition: lon,n -> list-of-three-lists-of-nums ; (define (partition nums threshold) ; (local [(define (help nums a b c) ; (if (null? nums) ; (list a b c) ; (cond [(> (first nums) threshold) ; (help (rest nums) a b (cons (first nums) c))] ; [(= (first nums) threshold) ; (help (rest nums) a (cons (first nums) b) c)] ; [(< (first nums) threshold) ; (help (rest nums) (cons (first nums) a) b c)])))] ; (help nums null null null))) Nagging question: what is different about mergesort, qsort is different, that you'd expect would be the same? The base-case condition: null? vs length-zero-or-one? Why is this? (The pitfalls of generative recursion) Okay, you-third of the class write collect-lessers, you-third write collect-equals, ... What do these look like? USE OVERHEADS: (define compare-and-collect-greater (lambda (ref lon) (cond [(empty? lon) empty] [else (if (> (first lon) ref) (cons (first lon) (compare-and-collect-greater ref (rest lon))) (compare-and-collect-greater ref (rest lon)))]))) (define compare-and-collect (lambda (n lon better-than) (cond [(empty? lon) empty] [else (if (compares-well (first lon) n) (cons (first lon) (compare-and-collect n (rest lon) better-than)) (compare-and-collect n (rest lon) better-than))]))) ... [include a small error, to fix later, *after* copy/pasting] To write these, you'd copy-and-paste, leaving you with three copies of the code to maintain. What if you find an error? What if you find a better way (say, you want to make ti tail-recursive)? Moral: Don't copy code! How could we avoid this? We could have written many different functions collect-greaterthan-eight, collect-greatherthan-nineteen, ... but of course we didn't; we passed in the desired number as an argument. When we applied the function, the box for "num" was replaced with the desired argument. Same thing here: we'll just pass in <, =, or > as an arguemnt, which will fill in the box when the function is applied. (define (collect-according-to compare reference) 0 (define (collect criterion candidates) ...) ---- 98.mar.09 Functions are people too: coin-toss: list-of-{'head,'tail} --> num (define (coin-toss tosses) (cond [(null? tosses) 0] [else (if (head? (first tosses)) (add1 (coin-toss (rest tosses))) (sub1 (coin-toss (rest tosses))))])) We could re-write: ...(+ (coin-toss (rest tosses)) (if (head? (first tosses)) +1 -1)) But even more like the spirit of original ...((if (head? (first tosses)) add1 sub1) (coin-toss (rest tosses))) Now, reconsider what we did to combine collect-greater, collect-less, collect-even: We looked at three copies of code, circled the common part, and made it an arg. Recall collect-evens. Can we reduce the replication of code? (define (collect lyst keep?) (cond [(null? lyst) null] [(keep? (first lyst)) (cons (first lyst) (collect (rest lyst) keep?))] [else (collect (rest lyst) keep?)])) Now in merge-sort we have: (append (collect lon (lambda (n) (< n (first lon)))) ... Exercise: you have written less-than?. Now write greater-than?, equal-to?. child-older-than?, ... Aha, find the common part, and abstract! Exercise: Write less-than-5?. Write less-than-9?. Write (less-than? n) What are the contracts? Example: (define less9? (make-lesser 9)) (less9? 3) = ...(to be continued) ---- This is called *currying* <. < wants two arguments, but we only have one at the moment. So package one argument into the lambda-suitcase, and wait for the other one to get plugged in. Contract. Finally, make-predicate: (define make-predicate (lambda (comparer? reference) (lambda (n) (comparer? n reference))) (define (lesser-than n) (make-predicate < n)) We could also write: (define (child (child-year a) (child-year b))) We are *currying* <: ---- oct.26 mar.11 Easy Q: do you need to name a function to apply it? A: 'cousre not ((lambda (x) (+ x 19)) 3) = 22 Remember map, from lab: (map sqrt (list 16 25 1 4)) = (list 4 5 1 2) (Could you write map yourself? easily.) In lab, and end of last lecture, we say currying: (define add19 (lambda (n) (+ n 19))) You might wonder why? Why not just call + directly, once you have both args? A couple of examples: In miss&cann, you wanted to take a single state and return five successor states. (ahem) Clearly, you'd call one function five times: (define states-in-1-trip (lambda (s) (list (make-trip s (make-boatload 0 2)) (make-trip s (make-boatload 0 1)) (make-trip s (make-boatload 1 1)) (make-trip s (make-boatload 1 0)) (make-trip s (make-boatload 2 0))))) where make-trip: state,boatload --> state What's even a bit more general? (define all-boatloads (list (make-boatload 0 2) ...)) (define states-in-1-trip (lambda (x) (map ...make-trip... all-boatloads))) (define derivative@ (lambda (f x) (local [(define dx 0.00001)] (/ (- (f (+ x dx)) (f x)) (- (+ x dx) x)))) (What's the contract?) But i want derivitive-of: ;; derivative: (R-->R) --> (R-->R) [ similarly, hw has "(integrate f a b eps)"; using that you can write one-line "(integral-of f)", or rather "(integral-of f c eps)", where this function applied to x gives the integral from c to x...c corresponds to (pre-image) of C. ] /* * Other examples where currying is useful: * generating sort functions, w/o needing to give a list: * (make-merge-sorter <=) * (make-insert-sorter <=) * You can pass one sorter or another, depending on how it will be used. */ In general (define make-list-fun (lambda (base combine) (local [(define fun (lambda (lyst) (cond [(empty? lyst) base] [(cons? lyst) (combine (first lyst) (fun (rest lyst)))])))] fun))) Exercise: write this w/o local! ------- 98.oct.28 What is the contract of make-list-fun? (define sum (make-list-fun 0 +)) (define contains-mark? (make-list-fun #f (lambda (frst-name in-rest-of-list) (or (eq? frst-name 'mark) in-rest-of-list)))) (define collect-greater7s (make-list-fun empty (lambda (frst-num bigger7s-in-rest) (if (> frst-num 7) (cons frst-num bigger7s-in-rest) bigger7s-in-rest)))) Write collect, using make-list-fun! (define collect (lambda (lyst keep?) (make-list-fun empty (lambda (frst good-rest) (if (keep? frst) (cons frst good-rest) good-rest))))) Okay, changing the world: Okay okay, the main topic: CHANGING THE WORLD We had an assignment with bouncing balls; we moved a ball by creating a new ball which coincidentally was related to a previous one. This doesn't reflect how we real-world think about the problem... The language provides a remedy: (define-struct posn (x y)) (define profPlace (make-posn 3 5)) (set-posn-x profPlace 17) profPlace = (make-posn 17 5) This is very different from anything before! The rule of set-posn-x! is to evaluate each argument (first), then set the field appropriately. It does not return a value (or rather, it returns the useless value (void)). Note that the important thing about calling set-posn-x! is not the return value, but the *side-effect* of calling the function. So how can we write move-ball!: ball --> 'moved-it (define-struct ball (place velocity color name owner size ...)) Remember that the old way would be to take in a ball, and return a new one which had copies of most of the fields w/ a few changes. (define (move-ball! b) ... (set-posn-x! (ball-place b) (+ (posn-x (ball-place b)) (posn-x (ball-velocity b))) -------- oct.30 (fri) ;; uh-oh, how to change the y component? ;; and return a symbol Notice we need to call set-posn-x!, and also return a value. Use "begin", which takes in a list of expressions, evaluates each, and returns the value of the final expression. (This is pointless unless the non-last expressions engender side-effects.) Exercise: Write move-lob!: list-of-balls --> 'wahoo Hint: follow the template! (define (move-lob! lob) (cond [(null? lob) 'wahoo] [else (begin (move-ball! (frist lob)) (move-lob! (rest lob)))])) ---- mar.18 semantics of set--! If Elizabeth Windsor dyes her hair, what happens to the queen of england? She has purple hair too! (define-struct person (name hair-color birth-year eye-color hair-color height weight ...)) (define liz (make-person 'elizabeth 'brown 1926 ...)) (define queen-of-england liz) (set-person-hair-color! liz 'purple) (person-hair-color queen-of-england) = 'purple How does this work? As it stands, the rules don't explain it: (set-person-haircolor liz 'purple) = (set-person-haircolor (make-person ...) 'purple) and how do we know that this make-person is the same liz, or just somebody with the same name? (we want to be able to model both) Here is the truth: Whenever you call make-person (or make- or vector or cons), what gets returned is not a box with those entries, but actually a *reference* to a box with those entries. A reference is a new type of value. We'll indicate references with hats: (define liz^ (make-person 'elizabeth 'brown 1926 ...)) (define liz liz^) Now, when we evaluate (define queen-of-england liz), here's what happens: (define queen-of-england liz) = (define queen-of-england liz^) ;; stop here; liz^ is a reference. (define liz^ (make-person 'elizabeth 'brown 1926 ...)) (define liz liz^) (set-person-haircolor! liz 'purple) = (set-person-haircolor! liz^ 'purple) = (void) ;; but we re-write the make-person associated with liz^ This seems like a bit of superficial syntactic wordplay, but it makes a huge difference. What if we'd said: (define liz (make-person ...)) (define queen-of-england (make-person ...)) (set-person-haircolor! liz 'purple) Note that references are created whenever you make a structure, even if you don't associate a placeholder with it. Set--! does *not* change the value of any placeholder. <<<<<<<< set! intro and this sequence was deferred to lab. Now, to make the difference clear or to confuse it hopelessly: "define" associates a placeholder with a value. You can modify such an association with "set!". (This is very different!) (define x 17) (set! x 23) ;; semantics: all further references to x use the value 23 Example: (define l 'common) ;; evaluate r.h. value; any knowledge of "x" is gone. (define q l) (set! l 'purple) q Compare with the previous example where q was queen-of-england, l was liz >>>>>>>>>>>>>> [finished 4min early!] ---- circular structures, depth-first search with marking 98.nov.02 98.mar.20 Recall the family-tree example: Can we distinguish between having : - two different people who happen to have the same name, birthyear, eye-color and (say) 'unknown parents, vs - the same identical person, referred to twice? For instance, (define gabi (make-child 'gabi 'unknown 'unknown)) (define chantelle (make-child 'chantelle gabi ...)) (define pierre (make-child 'chantelle gabi ...)) Given the values of (the placeholders) chantelle and pierre, can we conclude they are siblings, or just that they happen to both have a mother with the same name? You won't need to know the term, but we have a name for these two different notions of equality: Are chantelle's mother and pierre's mother equal "intensionally"? (i.e., the same identical person), or "extensionally"? (two structures which happen to have equal values in each field; i.e. if you take either structure and "extend" it (compute some result based on its value), you get the same answer). A hand-eval says they are intensionally equal, and we have indeed followed the guideline of one call to make-child for each actual human we model. What is an experiment you can do to confirm or deny? [Hint: we could not have done this experiment before the previous lecture, when we didn't have set--!. So it was a moot question before now.] Also, recall the airline path-planning we did: We wanted a city-structure to be a name and a list-of-cities (nhbrs). But then we couldn't define cities that required cycles. (Our solution then: a city just contains the *names* of its nhbrs, and we had to do some global lookup to map names to actual cities. Hmmm, really this level of indirection achieves exactly the indirection of reference-values.) How can we do this now, that we know about mutating structures? Well now, we can make what we really want: ; A is ; (make-city ) (define-struct city (name nhbrs)) (define theWindyCity (make-city 'chicago null)) (define ricesTown (make-city 'houston (list chicago))) (set-city-nbhrs! theWindyCity (list ricesTown)) What if we want to have a sightseeing flight from chicago to itself? (set-city-nhbrs! theWindyCity (cons theWindyCity (city-nbhrs theWindyCity))) Aside: this representation of cities is more natural: the nbhrs are cities. [ What does drScheme print? Try it in lab! ] What about handling loops in the path-from problem? We can actual use the method you'd probably use in real life: as you reach a city, mark it as visited; whenever you visit a city first see if it's been marked. What do we need to change? (a) add a field "visited?" to the structure. (b) when making structures, initialize with #f (or have a helper do this). (c) In the algorithm, when we visit a city, check whether it's been marked. (d) In the algorithm, mark a city before proceeding with its neighbors. The sequence of (c) and (d) is important. ;; path-from!: city, city --> union(list-of-city, #f) ;; Return either any path src to dest, or #f if none. ;; ;; Note: Calling this function has the side-effect of ;; changing the "visited?" flag of (perhaps) many cities. ;; (define (path-from! src dest) (cond [(eq? (city-name src) (city-name dest)) (list src)] [(city-visited? src) #f] [else (begin (set-city-visited?! src #t) (local [(define subpath (path-from-any! (city-nhbrs src) dest))] (if (list? subpath) (cons src subpath) #f)))])) ;; path-from-any! list-of-city, city --> union(list-of-city, #f) ;; Return either: any path from any city in src-list to dest, or #f if none. ;; ;; Note: Calling this function has the side-effect of ;; changing the "visited?" flag of (perhaps) many cities. ;; (define (path-from-any! src-list dest) (cond [(empty? src-list) #f] [else (local [(define a-way (path-from! (first src-list) dest))] (if (list? a-way) a-way (path-from-any! (rest src-list) dest)))])) -------- 98.nov.04 (wed) vectors: Vectors are structures, except that you access fields by number, not by name. (N.B. count from zero.) (define data (vector 'four 'score 'and 'twenty 'years 'ago)) (vector-ref data 2) = 'score (vector-ref data 6) : error (define nums (vector 10 12 14 16 18)) Write a function ;; sum-vec: vector(v), natNum(n) --> num ;; Return the sum of the first n elements of v: i.e., v[n-1]...v[0] When should you use vectors of info, as opposed to lists of info? - When all the info has uniform meaning: e.g. pos[t] is the position of the car after the t'th second. - When accessing the k'th item is important (RAM) - When you know how many there will be in advance. Two other ways to construct vectors: (make-vector ) (build-vector ) Ex: Create a vector of 4 elements, so that the i'th element is i^2. Now change the 4th element (element#3), so it is 29. Write make-vector, using build-vector. And vice versa. (define (build-vector sz rule) (local [(define ans-vec (make-vector sz #f)) (define (fill-vec! v n rule) ...)] (begin (fill-vec! ans-vec sz rule) ans-vec))) It just remains to write fill-vec!: vec,natNum,(natNum-->value) --> (void) which sets the first n elements according to "rule": -------- 98.nov.06 (fri) (define (fill-vec! v n rule) (cond [(zero? n) (void)] [else (begin (vector-set! v (sub1 n) (rule (sub1 n))) (fill-vec! v (sub1 n) rule))])) (Is this tail-recursive? Yep.) Could we write this going from 0 up to n-1, instead of n downto 0? Sure! (define (fill-vec! v n rule) (fill-vec-helper! v 0 n rule)) (define (fill-vec-helper! v i n rule) (if (>= i n) (void) (begin (vector-set! v i (rule i)) (fill-vec-helper! v (add1 i) n rule)))) Note, there are really two different things here: (a) given i, do a task involving i: (vector-set! v i (rule i)) (b) rig the function to do this task, with i being 0, 1, 2, ..., n-1. Hey, let's separate these two things explicitly. Example: Write some code which takes a vector "nums" of numbers, and triples each value: (fori= 0 (vector-length nums) (lambda (i) (vector-set! nums i (* 3 (vector-ref nums i))))) ;; fori=: num, num, (num-->(void)) --> (void) ;; (define (fori= start-val stop-val body!) (if (>= start-val stop-val) (void) (begin (body! i) (fori= (add1 start-val) stop-val body!)))) Think about: what if we frequently wanted to do something to every other number: should we modify the task (body!), or the control-logic in charge of applying body! to the right values? Both ways work; the latter is better. (See end of nov.09 lecture, for what to talk about next.) ---- [swap this lecture and next; I need to have intro-to-JAM before the lab] 98.nov.11 (wed.) Think about: what if we wanted to cumulate the vector? What if we want to triple every-other number? (Or, we wanted to cumulate downward?) A more general version of fori=: give it a starting value, a way to generate the next number, and a way to know when it's done: /* WHETHER TO CONTINUE (for C analogy) */ Example: (fori= n zero? sub1 ...body...) You write: a version which uses this new improved fori= to call the body with every-other value of i: - what is the stopping condition? How to get the next value? (fori= 0 (lambda (i) (>= i (vector-length nums))) (lambda (i) (+ i 2)) ...body...) (define (fori= start stop? next body!) (if (stop? start) (void) (begin (body! start) (fori= (next start) stop? next body!)))) We'll return to this in a while, to use fori= to even help with non-vectors. Pragmatics of set! (a) keep track of state (b) keep track of history (similar to (a)) (c) imperative programming Using set! for history: Consider J-mart: We had a global placeholder: (define total-inventory (list ...)) Which was then used by other functions: (select-whites ... total-inventory) Now, write receive-item!: (define (receive-item! new-item) (set! total-inventory (cons new-item total-inventory))) This is using set! to keep track of state. Note that total-inventory is global. Is this good? To think about: how might it be fixed? Another example: A random-number generator: (define seed 0) ;; return a pseudo-random int in [0,100) (define (rand) (begin (set! seed (next-num seed)) seed)) ;; next-num: number --> number ;; (define (next-num n) (remainder (+ 17 (* n 41)) 100)) 17, 14, 91, 48, 85, ... (NB if i change 41 to 43, i get 17,48,81,0,17,...) Of course, the limit 100 could be made more flexible: (define max-rand 100) How could we make it so ? (b) An alternative use: keep track of history. You are disgruntled, about to quit, and you write code that will stop working after 100 times (just returning 'haha instead of answer). Version I: (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...)))) Version II: 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, Cf. (define x (+ 3 4)) Is + called every time x is used, or just once? Answer: The way define works, just once. In our case "local" is analagous to "+". It is only called once, it returns a value (a lambda involving times-called%473), and referring to the placeholder just looks up this lambda. The *local* is evaluated only once. 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: Cf. (define function-x (lambda () (+ 3 4))) Now, every time function-x is called, the + is evaluted. [not mentioned: When is an example when we might want indeed to have the local be insdie 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? (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]))))) How to fix it? ] (c) Imperative programming: In scheme, the fundamental unit of computation is function-call. (A functional language) In imperative langauges (JAM, C, Java), the fundamental unit is assign-to-variable (and sequence), with a background emphasis on calling functions. -------- nov.09 (mon): JAM I: (unfortunately this preceded the above lecture, 98.fall) We have talked about computation at a high level, leaving undefined (a) the primitive functions +,cons, ..., and (b) how to carry out law-of-scheme like "look up placeholder's value" or "re-write body with substitutions...". Here is an alternate, low-level view of computing. "low-level" means that you need to understand the actual machine hardware before understanding the language. The JAM machine -- a model of all modern electronic computers -- has several parts to it: [picture] - memory, a vector with locations 0..65536, holding only numbers. RAM. Refer to contents of i'th position as mem[i]. - ten other memory locations, "registers" R0...R9, Conceptually, these are just like memory. and another special-purpose register "PC", program-counter. - CPU, "central processing unit". The CPU operates on a four-step cycle: 1. fetch mem[PC] and remember it. 2. decode the fetched value as an instruction. 3. increment PC. 4. execute the instruction. Instructions are things like "multiply the contents of R3 and R5 and put the result into R0 (R0 <-- R3*R5)", "put the number 17 into R4 (R4 <-- 17)", and "R8 contains a memory address: put the value of R0 into the indicated location in memory mem[R8] <-- R0". The memory holds both data, *and* instructions. Wait a minute -- since memory can hold only numbers, we have to encode instructions as numbers. Some examples of encoding (see reference sheet): (add R3 R8 R9) (meaning: R3 <-- R8 + R9) is encoded as 00098310. (ldi R4 156) (meaning: R4 <-- 156) is encoded as 15649. (Note when i say ninety-eight thousand, three hundred and ten, this is actually a (lame) pun: the encoding is a sequence of digits. In arabic numerals, numbers are also encoded as a sequence of digits.) Note that the only ultimate work we do -- adding (and branching?), is done in registers. We need to transfer between main memory and registers and back: we'll see the meaning of "ld" and "st" later. Lab will introduce the JAM-inspector, which lets you change memory contents, and *then* start the actual JAM machine running. (I had 8 more min; i defined fori=-v2, but didn't get a chance to use it.) We want a version for fori= that, if needed would count by two, or even by -1. This entails a more complicated stopping-expression. ;; (define (fori=-v2 start-val stop? next body!) (if (stop? start-val) (void) (begin (body! start-val) (fori=-v2 (next start-val) stop? next body!)))) What is the contract? Ex: use fori=-v2 to write "countdown" which returns (void), but prints statements. Ex: Suppose somebody gave you a language with *only* fori=-v2. How would you write the original fori=, which just used integers? -------- nov.13 (fri) JAM II (c) imperative programming: (define x 3) (define y 17) ;; ... (set! x (- x y)) In JAM: ; Choose to store x at location 100 ; Choose to store y at location 200 ; R0 holds a copy of x ; R1 holds a copy of y ; R2 holds the address of x, 100 ; This version sneaky: no copy of 200 kept around! ; R4 stores x-y (ldi R2 100) ; R2 <-- 100 (ld R0 R2) ; R0 <-- Mem[R2] = Mem[100] = x (ldi R1 200) ; R1 <-- 200 (ld R1 R1) ; R1 <-- Mem[R1] = Mem[200] = y (sub R4 R0 R1) ; R4 <-- R0 - R1 = x - y (st R4 R2) ; x <-- x - y (halt) Now: Compute the sum of data[0]...data[size-1], where data and size are already-defined placeholders. We have seen how to do this with template, you could do it accumulator style, and even with fori= using (set! sum-so-far ...). [do these as an exercise] (define data (vector ...)) (define size (vector-length data)) ; It's 10. ;; sum-data-to-size: --> number ;; An imperative-style program, to sume data[0]...data[size-1]. ;; (define i 0) (define sum-so-far 0) (define (sum-data-to-size) (if (< i size) (begin (set! sum-so-far (+ sum-so-far (vector-ref data i))) (set! i (+ i 1)) (sum-data-to-size)) sum-so-far)))) ; (How to make sure you can run this program twice?) What is this in JAM? addr assembly comment ---- ---------- ------- ; R0 will hold "i", which will count 0..size-1. ; R1 will hold "size", which is 10. ; R2 will hold "sumSoFar", initially 0. ; R3 will hold the constant 1. ; R7 will hold 200: where "data" starts in mem ; R8 will hold R7+i. ; R9 will hold "data[i]". [186]: (ldi R0 0) ; i <-- 0 [187]: (ldi R1 10) ; size <-- 10 [188]: (ldi R2 0) ; sumSoFar <-- 0 [189]: (ldi R3 1) ; r3 <-- 1 [190]: (ldi R7 200) ; [191]: loop: (sub R4 R0 R1) ; Compare i with size (actually, compute i-size) [192]: (bgez R4 198) ; If i-size >= 0, branch to label "done" [193]: (add R8 R7 R0) ; R8 <-- R7+i, the *address* of data[i]. [194]: (ld R9 R8) ; R9 <-- data[i] [195]: (add R2 R2 R9) ; sumSoFar <-- sumSoFar + data[i] [196]: (add R0 R0 R3) ; i <-- i + 1 [197]: (jmpi 191) ; Go back and repeat. [198]: done: (print R2) ; Print sumSoFar [199]: (newline) Any questions on the above? Here are a few that come to mind: Yes, if you look at the reference sheet, you can see that you can use "ldx" to accomplish lookup of data[i] (for which we current uses an "add" followed by "ld"). Wouldn't it be nice to write "(jmpi loop)", instead of "(jmpi 191)"?, making use of the semantic label we gave to the instruction living at 191. Alas, the low-level JAM assembly-language doesn't understand this, but sometimes we'll write this higher-level version on the board anyway. This code works fine, up until the last moment, when chaos then ensues. Why? N.B. Keep in mind that printing a value and having a function return a value are different. In drscheme, when you type in an expression, it computes the value, and at the very end prints it out. (Many other values were being returned from (recursive) function calls, which never got printed, thank goodness.) In many imperative languages (JAM, C, Java), nothing ever gets printed for you; you must always explicitly call print. We will walk through the above code. This is alwasy the ultimate method of debugging: see what the code does (not what you think it does). [I got through the variable declarations.] ---- 98.nov.16 Finishing walking through the above JAM program. Remember, what Scheme program it came from. [show slide] Going over the JAM program: Hmmm, these bgez/jmpi instructions are new. What line of scheme code does the instruction "(jmpi loop)" correspond to? Why is the order of CPU-steps important (why can't you swap steps 3,4?) Why does the code go into oblivion, at the end? Question: was the Scheme version tail-recursive? Yep. ---- 98.nov.18 Loop, is when you have this jmpi thing, as seen in last program. Tail recursion = loops. Consider: ;; avg-pos: vec(vector of num), i(natNum), sum-so-far(num), num-pos(natNum) ;; --> number ;; Return the average of the positive numbers in vec[0]...vec[i-1], ;; where we've previously seen num-pos such numbers with accumulated sum-so-far. ;; (define (avg-pos vec i sum-so-far num-pos) (cond [(zero? i) (/ sum-so-far num-pos)] [else (if (positive? (vector-ref vec (sub1 i))) (avg-pos vec (sub1 i) (+ sum-so-far (vector-ref vec (sub1 i))) (add1 num-pos)) (avg-pos vec (sub1 i) sum-so-far num-pos))])) What is the JAM version? Outline: 3999: ; 1. 4000: ; i = vector-size, 4001: ; sum-so-far = 0, 4002: ; num-pos = 0. 4003:loop: ; If (i == 0), then branch to "end". 4004: ; Load vec[i-1] from memory, and 4005: ; If (vec[i-1] <= 0), then jump to "skip-update". 4006:update: ; sum-so-far = sum-so-far + vec[i-1] 4007: ; num-pos = num-pos + 1 4008:skip-update: ; i = i - 1 4009: ; jump to "loop" 4010:end: ; print answer. [Questions? Could you write this?] What happened to the tail-recursion, with two accumulated arguments? That's the loop. Note what we had: it was nicer to talk about variable names, than register names, and do "if..." statments in one line. And again, a for-loop would be nicer yet, to separate out the control-flow. C is a high-levle language (imperative style), providing these things. It is also a low-level langauge, granting access to memory, and needing understanding of machine to really understand programs. Consider a C program: Variables: - (define x 3) int x; // declare x = 3; // assign // OR int x = 3; // initialize factorial. -------- 98.nov.20 Write sum (of first n integers). - based on template - what does accumulator version look like, with accum as arg? . [ with accum as variable? ] -- [No; too gross.] - with a for-loop. arrays: vectors were 1-dim (rowhouses); arrays are 2-dim (hotels). Example: - spatial dimensions: a map, where map[r][c] has the terrain-type of cell at row r, column c. Pixels. - time: is jail-cell i open at time t? jail[i][t] (suppose you want to remember for all time) - abstract: vector of scores in the class. array of scores in all classes (score[i][c] is score of student #i in class #c) array of scores in all classes in all semesters: score[i][c][s] ...of all schools... Conventionally, each dimension indexed 0...-1. Syntax in C: an array of avg-elevations of patches of houston: const int maxRows = 10, maxCols = 10; // useful constants double elev[maxRows][maxCols]; // A declaration: create the array in memory! ... I finished with: fill array with 0.0, using nested for-loop. ---- 98.nov.23 (mon) Warm-up ex: // Fill arr with "0.0", except along main diag put "1.0" int r, c; for ( r = 0; r < maxRows; r = r + 1 ) { // FillRow(r) for ( c = 0; c < maxCols; c = c + 1 ) { if (r==c) arr[r][c] = 1.0; else arr[r][c] = 0.0; } } Note the nested loop: r is 0; c goes 0...maxCols. Then r is 1; c goes 0...maxCols. (Remember, that the for-loop separates looping through values, from the particular task for i,j. E.g. your for-loop should not include problem-specific tasks inside the end-of-loop update.) Syntax in Scheme: (array-ref arr r c), (array-set! arr r c val), (make-array r c init-val), (build-array r c init-fn) (array-num-rows arr), (array-num-cols arr) What would init-fn be for the identity matrix? (Do we care *how* scheme represtents arrays? eg a list, one long vector? No -- and we don't even *want* to know.) Actually, these aren't built-in. How would you write them? A vector of vectors. [Draw Picture; include references.] [my picture: initial vec is column; arr[2] is a row.] Example: Write array-num-rows; array-num-cols. (define (array-num-rows arr) (vector-length arr)) (define (array-num-cols arr) (vector-length (vector-ref arr 0))) ;; Whoop whoop whoop: what if zero rows?! Write make-array: (define (make-array m n init-val) (make-vector m (make-vector n init-val))) Looks nice, but it has a problem!!!! (If we'd used the value (+ 3 7), how many times is + eval'd?) Thus, how many times is make-vector eval'd? How many vectors made? Only 1 [Draw Picture] Fix: (define (make-array m n init-val) (build-vector m (lambda (i) (make-vector n init-val))) Exercise on your own: write array-ref. ---- 98.nov.25 (wed pre-gobble) Digressions day: When to use quotes, in English: My name is mud. (My band's name is hard to pronounce.) She asked how to play kazoo; I replied quietly. Love is a four-letter word. What is the center of gravity? Use of quotes: Supresses matching a meaning(value) with the word(placeholder). (Also: to indicate a term defn, or re-defn, or abuse/novel-use, sarcasm, even quoting: A cat's "thumb" is on its forearm. When writing, look at each use, is it justified? NOT: "thanks", the "brilliant" decision... Names automatically considered quoted: New York.) ", when prepended to an unquoted copy of itself, becomes grammatical." Examples of poor interface design: - most-used/important buttons should be biggest. (nuclear power panels w/ rows of buttons; keg handles.) (CD player: play. Display should default to time-left.) - course-eval web page: use colors, not numbers, and fixed column heads. When to count from zero instead of 1: (My microwave, which goes for n+1 seconds) - when counting continuous quantities: distance after t seconds; height of a building - from 1 with discrete quantities: # of people in room, # of floors in a building Note that there is sometimes conflict: european ground floor=0. This is great in that floor-k is 10k feet above ground (and you climb k flights); however a building with k floors is 10(k+1) ft tall. (Are you more interested in floors or ceilings?) Chapter 0 of a book -- only if it contains background material. (What if there is background stat + background econ?) (Undigress, but only for a moment.) [Show only this first version.] How to loop through the array, in scheme? ;; sum all elements of arr, from arr[i][j], arr[i][j+1], ... onward, ;; (define (sum-array-help arr i j) (if (>= i (array-size-rows arr)) 0 (if (>= j (array-size-cols arr)) (sum-array arr (add1 i) 0) (+ (array-ref arr i j) (sum-array arr i (add1 j)))))) ;; Alternate, positive version: [do not show] ;; (Do not display? All our template examples asked the ;; question 'when to stop', not 'when to continue'.) (define (sum-array-help arr i j) (if (< i (array-size-rows arr)) (if (< j (array-size-cols arr)) (+ (array-ref arr i j) (sum-array arr i (add1 j))) (sum-array arr (add1 i) 0) 0))) DO NOT DISPLAY (it's a bit roundabout?): ;; sum all elements of arr previous to (but not incl) arr[i][j]. ;; E.g. arr[i][j-1],... ;; (define (sum-array-help arr i j) (cond [(and (zero? i) (zero? j)) 0] [(and (positive? i) (zero? j)) (sum-array-help arr (sub1 i) (array-size-cols arr))] [else (+ (array-ref arr i j) (sum-array-help arr i (sub1 j)))])) -------- With scheme for loops (do not necessarily show?): (define (sum-array arr) (local [(define sum-so-far 0)] (begin (fori= 0 (array-size-rows arr) (lambda (r) (fori= 0 (array-size-cols arr) (lambda (c) (set! sum-so-far (+ sum-so-far (array-ref arr r c))))))) sum-so-far))) C version: double sumArray( double arr[numRows][numCols] ) { double sumSoFar = 0; for ( int r = 0; r < numRows; r++ ) for ( int c = 0; c < numCols; c++ ) sumSoFar += arr[r][c]; return sumSoFar; } Previous question: how do we pass a (2-D) array to a function? Why i say "reference" rather than "pointer": - In C, a memory address, or porinter, is an explicit type... and you can do arithmetic with pointers! (No real need to.) So "poitner" has connotations with arithmetic and particular numbers. - A "reference" means just an arrow; i don't know/care about the particular number, and for mercy's sake wouldn't dream of multiplying them. It is seriously distressing, when programmers use the improper paradigms, saying "it works"; this is what leads to many existing problems. ---- 98.dec.02 (wed.) Even though i'll use C syntax, this could all be equally well in scheme. (In last semester's Connect5 project, two entries tied for first: one in C, one in Scheme.) Open questions: - A C equivalent to symbols?? (Color, in prev example.) - How to pass two-d arrays? - Why does C require the * with structures, to get Scheme semantics? What does it do if the * is omitted? In Scheme we'd use symbols. C has enumerated types: enum Color { fuchsia, teal, sienna }; examples: Color myFave; myFave = teal; if (myFave == sienna) ... [A fair amount of questions, after i said they print as ints but you shouldn't use them that way. ...(except maybe enum Month)... can one have enum VacationCities { sienna, ...}?] We want to have a map, a 2-d array, where each location is swamp, forest, mountains, plains, water. My property is a swamp. How would you represent this in a program? Land myProperty; myProperty = swamp; if (myProperty == island) ... Array: const int numSouth = 20; const int numEast = 30; Land map[numSouth][numEast]; // Still some questions on what "Land" meant. Okay, we want to take map, and every plains next to an island becomes a swamp. How to do this? erode: Land[][] --> void; modify Land. void erode( Land world[nS][nE] ) { for/for/floodland( world, r, c ); } void floodland( Land world[nE][nS], int r, int c) { if ((world[r][c] == plains) && nextToIsland(world, r, c)) world[r][c] = swamp; } [Got a question about effiencey: "isn't it better to check for islandness *before* calling nextToIsland, to avoid extra calls?"] bool nextToIsland( Land world[nE][nS], int r, int c) { // YUCK!: world[r-1][c-1] == island || ... // We want do something for a two-d region. Sound familiar? for ( int dr = -1; dr <= 1; dr++ ) for ( int dc = -1; dc <= 1; dc++ ) if (validIndices( r+dr, c+dc, nE, nS ) && (world[r+dr,c+dc] == island) && !(dr == 0 && dc == 0)) return true; // Else we continue with the for-loops. return false; } One more problem: Uh-oh, this changes world as we are still looking at it! (An artifact of looping l-r,top-bottom unlike reality.) [A student also suggested, adding a border to 'world'.] -------- apr.15 (tax) (define map (make-array 10 10 'plains)) (define (erode world) (for-simple 0 (array-rows world) (lambda (r) (for-simple 0 (array-cols world) (lambda (c) (floodland r c)))))) alluded to: %%% (define (for-vector vec body!) %%% (fori= 0 (lambda (i) (>= i (vector-length vec))) add1 body!)) (define (for-simple a b body!) (fori= a (lambda (i) (>= i b) add1 body!))) (erode map) ---- 98.nov.30 (swapped w/ previous lecture, for sake of lab :-() apr.17 (fri) struct InventoryRec { float cost; int numInStock; Color color; }; void printInventoryRec( InventoryRecord* ir ) { cout << "Cost is " << ir->cost << endl; } void discount( InventoryRec* ir, float p ) { ir->cost = ir->cost * p; } int main() { InventoryRec* itm = new InventoryRec; itm->cost = 9.3; itm->color = blue; cout << "It costs " << itm->cost << endl; printInventoryRecord( itm ); return 0; } Recursive structures: Cons cells. - - - - #include // for NULL #include struct Cons { int first; Cons* rest; }; Cons* makeCons( int car, Cons* cdr ) { Cons* c = new Cons; c->first = car; c->rest = cdr; return c; } void printList( Cons* c ) { if (c == NULL) cout << endl; else { cout << c->first << " "; printList( c->rest ); } } int main() { Cons* nums1, *nums2; nums1 = new Cons; nums1->first = 17; nums1->rest = NULL; nums2 = makeCons( 15, makeCons( 16, nums1 ) ); printList( nums2 ); } - - - - NOTE: our recursive idea that A List is - null, or - makeCons( kar, kdr ) is not part of the language! In fact, if a "null list" had been a structure (maybe all lists keep track of their own length), we'd have trouble distinguishing makeNulls from makeConss. What is going on here? A Cons* is a reference (signpost); a Cons is the real structure. (In Scheme you couldn't make this distinction; in C you can, and you must be careful not to confuse them.) Notice how discount actually changed the structure referred to; compare this with a function taking an int. Let's write allEven: Don't forget templates! bool allEven( Cons* l ) { if (l == NULL) return true; else return (l->car % 2 == 0) && allEven( l->cdr ); } You're not allowed to forget about program templates! ---- C structures: Under the hood or, The truth about splats and dots Questions unanswered: - how to pass two-d arrays? - why does C make us use *, to get scheme semantics for structs? Recall how we use structures in C to get Scheme behavior: ...(example from prev. lecture) "ir->price" is shorthand for "(*ir).price" What does "*" mean? (a) In declarations: "InvRec *x" means that *x is an InvRec, and x is a pointer-to-InvRec (b) In executable statements: "x" refers to the (value of the) pointer, "*x" refers to the (value of the) int. Pictures of memory, when you make a struct: creating invRec, calling discount, makeInvRec. Note that in calling "z = 0.9; discount( ir, z )", any changes to "ir" do seem to be reflected, but changes to z aren't. Why? Because in JAM, when we call a function, the parameters are copied. This is "call-by-value". C lets you do smething Scheme didn't: declare a structure, instead of a reference-to-structure. (This complicates things; I suggest you only use pointers-to-structs.) invRec ir2; ir2->price = 99.99; float z = 0.9; discount2( ir2, z ); cout << ir2->price; // prints 99.99 Note that Scheme, and original C, has only call-by-value. Because we can pass references by value, sometimes we effect changes. This is a faux pass-by-reference; you can imagine a language where changes to parameters are *really* modifying the calling-variable (e.g., in discount2, modifying the second arg would change z.) But we can also fake this, using references. Some books call this pass-by-ref, but it's not really (just having pass-by-ref can't explain circular references, item in multiple lists, etc). Uses of pass-by-ref: when you want to return multiple values w/o struct. -------- dec.07 (mon) Ask about: how many going to lab. How many have finals on sat,sun? Calling functions in JAM: Suppose you had sqrt written at 5000. ...input in register (or, agreed memory location) ... we could put the return address in a pre-designated spot (perhaps even mem[5023], the return-instruction) ... trash some registers ... return value in a register This could work okay (original fortran), except that... no recursion allowed! Need to remember a whole stack full of pending recursive calls... use a "stack" (the adt of a scheme list). ---- apr.22 (wed.) Vectors in C: - vectors are represented as a poitner to the first element. const int sz = 5; int main() { int data[sz]; data[2] = 17; } Draw memory diagrams of what happens in this. Note that data[2] is translated to *(data+2). While the latter is still valid C, DO NOT USE IT! What about passing arrays? void print( int vec[] ) { for (int i = 0; i < sz; i++) cout << vec[i]; cout << endl; } Draw memory diagrams of what happens when main calls print(data). What if we were to assign to vec[2]? Notice that it changes, just like when structure-references are passed, even though we didn't use the "*" syntax at all. (an inconsistenC) What about returning arrays? Try: // WRONG int[] makeAndFillVec() { int retVec[sz]; for (int i=0; i < sz; i++) retVec[i] = 2*i; return retVec; } int main() { int data[]; data = makeAndFillVec(); print( data ); } Well, first a syntax problem: "int x[]" isn't a valid declaration; change them to "int* x" :-( But there is a much more serious problem: Draw memory diagrams. When we leave makeAndFillVec, local memory goes away, can get re-used. (Ex: data1 = makeAndFillVec(); data1[2]=17; data2 = makeAndFillVec(); You can see that data1[2] is no longer 17! data1 and data2 *happen* to poitn to same location.) Solution: In makeAndFillVec, don't declare the array as a local variable (never return address of a local variable). Instead, int* retVec; retVec = new int[sz]; This way retVec (the poitner) is a local var, but the actual array is long-lived, on the heap. Talk about exam. ---- 98.apr.25 (last day) Exam bits: -- If wanting to temporarily mark structures, just having the references be local won't work. Remember, each call to make-student creates one object. Write a function "clone" if you want...(when a student contains references, what does cloning mean?) -- minor: (if ... #t #f) Where have we been? Scheme - define, lambda, if, zero?, 0, add1 computation as an extension of algebra struct recursion -- mirror your input generative recursion accumulator recursion, loops side-effects: set-struct-field!, set!, print, read Assembly Jam, C In Jam, some opcodes are redundant. What would a minimal set be? In fact, which is more powerful -- Scheme or JAM? (why "car" and "cdr"?) In 1930's, people trying to formalize "computation" lambda calculus -- Church 1936 Turing Machine -- Turing 1936 recursive functions -- Go:del, Kleene 1936 deduction systems -- Post 1943 register machines (JAM) -- 1946 Church-Turing thesis: all notions of "computability" are the same! Can we compute anything we like? No: ;; Loop forever. (define (loop x) (loop (add1 x))) ;; halt: program, input --> boolean ;; return #t if prog eventually stops given inp; #f otherwise. ;; (define (halt prog inp) ...) I claim we can't write this. Proof by contradiction: Pretend we *could* write it, and reach a contradition (Assuming my broker wasn't lying, i have $1M in bank.) Okay, supposing we *could* write halt in scheme. Then, (define (twisted prog) (if (halt prog prog) (loop 17) #t)) ;; twisted: takes a program, and ;; loops forever if prog halts given itself as input; ;; returns if prog runs forever given itself as input. Question: what is (twisted twisted)? - Suppose it loops forever. Then (by description of twisted), "twisted halts given itself as input". - Suppose it returns: Then (by description of twisted), "twisted loops given itself as input". No matter what (twisted twisted) does, we have a contradiction; our supposition that we could write "halt" must be wrong. Where to go from here: CS is *not* about programming. ("CS is no more about computers than astronomy is about telescopes") Comp212: more programming, and program design; object-oriented (java) circuit design (elec 326); comp arch: comp/elec 320; Compilers, OS. networks, security. applications: databases, graphics, ai, (robotics). theory: comp280; comp481 (user-interfaces are usually given scant notice, yet are arguably the most important feature of any program people will use.) ============================================================= ============================================================= Other miscellaneous lectures, lectures fragements, notes from previous years: ============================================================= [ see handwritten notes matthias's last lecture; halting problem. ] (why "car" and "cdr"?) Why C is unsafe - lets you walk off end of array - can cast pointers - pointer arithmetic lets you access memory directly !! - too easy to erroneously return pointers to freed memory. double sumSoFar; int i; double sumDataToSize() { sumSoFar = 0; Examples not used (lab has an example of loop-w/o-vector) What is the sum of x^2/2! + x^4/4! + x^6/6! + ... + x^100/100! That is, sum_i=0..100 x^i/i! (define (cos x) (lambda (x) (begin (set! sum-so-far 0) (fori= 0 add2 (lambda (i) (> i 100)) (lambda (i) (set! sum-so-far (+ sum-so-far (/ (expt x i) (! i)))))) sum-so-far))) (Of course, we could do this w/o set!. Since we're going-by-two, it's not quite nat.recursion, though close. Accumulator style also straightforward. And we could write a fori= version which used these approaches but still separated the control from the task-for-each-i.) Compute the sum of a list of numbers: (No, since we'd then want to translate lists into JAM) (define (sum lyst) (local [(define sum-so-far 0) (define (find-answer) (if (empty? lyst) sum-so-far (begin (set! sum-so-far (+ sum-so-far (first lyst))) (set! lyst (rest lyst)) (find-answer)))))] (find-answer))) Well, actually, the imperative version would have a loop-structure, to replace the "define find-answer" and recursive call. <<<< Note: there is nothing special about having destructive operations as the body of the fori= loop; we could easily have a version which returned a value (the index), which could get used recursively. In fact, this would be like make-lists-fun, for generatvie recursion. miss-and-cann: (fori=-v.2 (list start-state) all-legal-states-one-trip-later contains-final-state? (lambda (x) 'dummy)) [actually, "while" might be better for this?? (while (list start-state) contains-final-state? all-legal-states-one-trip-later) ] However, we are stressing bodies that do set!, vector-set! as an intro to imperative prog: >>>>> ; Another exapmle: fill-vec!, using fori=. (define (fill-vec! v n rule) (fori= 0 n (lambda (i) (vector-set! v i (rule i))))) ; Yes, this could avoid passing in n, by using vector-length. --------------------------------------------- opcodes; loops Recall from the end of two lectures ago, The JAM machine has a CPU, memory (a giant vector), and registers R0-R9 general purpose, special register PC. The four-stage step of JAM machine: Repeatedly (1) fetch the value mem[PC] (2) increment PC (3) decode the value as an instruction (4) execute the instruction. Okay, now we'll talk about some of the exact instructions. Refer to the reference sheet. There are a few general classes of instructions: - arithmetic (only works on registers) - memory (move between memory and registers) - control (modify PC) Let's take an example. Suppose in Scheme we have variables a,b, and we wanted the equivalent of (set! a (- a b)) ;; a <-- a - b (Alternate notation) For the JAM assembly version, we'll suppose that a is stored in memory location 456, and b in location 789. Hmmm, the only subtraction command is "Rx <-- Ry - Rz". This means that we need to move a and b from memory into registers. Aside: When to count from zero instead of 1: (My microwave, which goes for n+1 seconds) - when counting continuous quantities: distance after t seconds; height of a building - from 1 with discrete quantities: # of people in room, # of floors in a building Note that there is sometimes conflict: european ground floor=0. This is great in that floor-k is 10k feet above ground (and you climb k flights); however a building with k floors is 10(k+1) ft tall. (Are you more interested in floors or ceilings?) Chapter 0 of a book -- only if it contains background material. (What if there is background stat + background econ?) When to use quotes, in English: My name is mud. (My band's name is hard to pronounce.) She asked how to play kazoo; I replied quietly. Love is a four-letter word. What is the center of gravity? Use of quotes: Supresses matching a meaning(value) with the word(placeholder). (Also: to indicate a term defn, or re-defn, or abuse/novel-use, sarcasm, even quoting: A cat's "thumb" is on its forearm. When writing, look at each use, is it justified? NOT: "thanks", the "brilliant" decision... Names automatically considered quoted: New York.) ", when prepended to an unquoted copy of itself, becomes grammatical." Examples of poor interface design: - most-used/important buttons should be biggest. (nuclear power panels w/ rows of buttons; keg handles.) (CD player: play. Display should default to time-left.) - course-eval web page: use colors, not numbers, and fixed column heads. Loops: first write map-vector The body, given i, mar.30 JAM II: opcodes; loops Recall from the end of two lectures ago, The JAM machine has a CPU, memory (a giant vector), and registers R0-R9 general purpose, special register PC. The four-stage step of JAM machine: Repeatedly (1) fetch the value mem[PC] (2) increment PC (3) decode the value as an instruction (4) execute the instruction. Okay, now we'll talk about some of the exact instructions. Refer to the reference sheet. There are a few general classes of instructions: - arithmetic (only works on registers) - memory (move between memory and registers) - control (modify PC) Let's take an example. Suppose in Scheme we have variables a,b, and we wanted the equivalent of (set! a (- a b)) ;; a <-- a - b (Alternate notation) For the JAM assembly version, we'll suppose that a is stored in memory location 456, and b in location 789. Hmmm, the only subtraction command is "Rx <-- Ry - Rz". This means that we need to move a and b from memory into registers. ;; R0 <-- mem[456]. A two-step process: (ldi 1 456) ; R1 <-- 456 (ld 0 1) ; R0 <-- mem[R1] ;; R5 <-- mem[789]. The same two-step. (ldi 6 789) (ld 5 6) ;; Now we can subtract! Put answer in R2. (sub 2 5 0) ; R2 <-- R5 - R0. ;; We did "a-b" (answer in R2), so now "a <-- R2", ;; i.e. "mem[456] <-- R2". Looking at the store command "st", ;; we need to have the value 456 sitting in a register. Is that so? ;; Fortuitously, yes. (st 2 1) ; mem[R1] <-- R2. (halt) So this is the program. Now we need to put it into memory, so that the JAM machines' 4-step program can get to it. We have seven instructions; we'll place those in, say, mem[4], mem[5], ..., mem[10]. But it turns out, memory can only hold numbers! We must refer to the sheet for the encoding. Consider "(ldi 1 456)". Reading of the sheet, we see that this is encoded as "+00045619"; store this value into mem[4] using the machine inspector. Similarly, "ld 0 1" is encoded as "??1?1060", eg 101060. Question: What is "(sub 2 5 0)" encoded as? Note how you have to use the insepector to load these numbers (instructions) into mem locations 4..10. Then, you must set the PC to 4, and execute. ---- apr.01 Scheme to Jam to C lab: Jam, C intro hw: Jam loops, simple C, C loops An Overhead slide, with the program from last time: ;;; (set! a (- a b)) ;;; a <-- a - b (Alternate notation) ;; We presume a is stored in memory location 456, and b in location 789. locat'n: Value ; Decoded ; Comment. mem[ 4]: 45619 ; (ldi 1 456) ; R1 <-- 456 Two-step for R0<--mem[456]: mem[ 5]: 101060 ; (ld 0 1) ; R0 <-- mem[R1] mem[ 6]: 78969 ; (ldi 6 789) ; R6 <-- 789 Two-step for R5<--mem[789]: mem[ 7]: 106560 ; (ld 5 6) ; R5 <-- mem[R6] mem[ 8]: 05220 ; (sub 2 5 0) ; R2 <-- R5 - R0. Subtract; put answer in R2. mem[ 9]: 101270 ; (st 2 1) ; mem[R1] <-- R2. R1 is still a's location. mem[10]: 321000 ; (halt) Note with ld, st: in "(ld 2 1)", R2 is the register which gets the value; R1 is the register which contains the *address* of the value. The actual values are "machine code". THe decoded instructions are "assembly code". It's tedious writing in machine code; much nicer to write in assembly, and then have a program to translate it. That's it first worked: somebody wrote an assembler, in machine code! Thereafter, you write in assembly, and give your english-like program text to the assembler, which translates it. (Actually, they probably wrote it in assembler, and then hand-evaluated to assemble their assembler into machine code.) We'll do a similar thing in lab: You'll write in C, and then an "compiler" will translate your C code to assembly code, which will then be assembled into machine code. (In drScheme, every time you hit "execute", this compiling happens.) We will see C soon, and in lab how to do the compiling (i.e. how to click the execute button.) C is "high-level assembly": it allows for variable names, for-loops, function calls. Here is an example which involves a loop. We want to store the values 0...49 in locations 100...149. (define vec (make-vector 50)) (define (fill i) (if (< i (vector-length vec)) (begin (vector-set! vec i i) (fill (add1 i))) (void))) In JAM-assembly: - Approach 1: have veci iterate 100,101,102,...149, and access mem[vec]. - Approach 2: set vec = 100; have i iterate 0,1,2,3,...,49, and store into mem[vec + i] We'll take approach 2; it parallels the Scheme (and C) approach. ;; We'll use memory locations 100 through 150 to store "vec". ;; First think about what placeholders we need. ;R0 i <-- 0 ;R1 len <-- 50 ;R2 vec <-- 100 ;R3 vec + i ;R4 uno <-- 1 ;R5 diff: ?? (ldi 0 0) (ldi 1 50) (ldi 2 100) (ldi 4 1) loop: (sub 5 1 0) ... ; is i < len ? (add 3 2 0) ; R3 <-- vec + i (st 0 3) (add 0 0 4) ; i <-- i + 1 (jmpi loop) done: (halt) Now go back and fill in the skipped line: blez 5 done Eleven instructions. Put them in memory 90..100. It works, but only because of luck! What if we set vec[i] to be i+1970? Lesson: be very careful! (Some machines have two memories -- prog & data) ---- apr.03 Exercise on your own: get rid of computing vec+i, and instead use the stx instruction. (stx 0 2 0) ;; mem[R2+R0] <-- R0 Cf Scheme and C: C has the contracts built-in.

Scheme, natural Recursion

(define size 50) (define nums (build-vector size (lambda (i) 17.3))) ;; max: natNum --> float ;; Given N, find the largest entry in nums[0]..nums[N]. ;; Note that since N is 0, this is always a non-empty set. ;; The natural recursion version. ;; (define (max N) (if (zero? N) (vector-ref nums 0) (local [(define max-of-rest (max (sub1 N))) (define nth (vector-ref nums N))] (if (> max-of-rest nth) max-of-rest nth)))) (max (sub1 size))

C, natural Recursion

// max, the natural-recursion version.
//
#include 
const int size = 50;
double nums[size];

// max: Return the largest entry in nums[0]..nums[N].
//
double max( int N ) {
  if (N==0)
    return nums[0];
  else {
    int maxOfRest = max( N-1 );
    int nth = nums[N];
    if ( maxOfRest > nth )
      return maxOfRest;
    else
      return nth;
    }
  }

int main() {
  cout << max( size-1 );
  return 0;
  }

// Note: you must call a function "fill-up-nums()" to
// initialize the array.

Scheme, accumulator version

(define size 50)
(define nums (make-vector 50 17))
(define (max N max-so-far)
  (if (>= N size)
      max-so-far
      (if (> nums[N] max-so-far)
          (max (add1 N) nums[N])
          (max (add1 N) max-so-far))))
(max 1 nums[0])

C, accumulator version

#include 
const int size = 50;
int nums[size];

int max( int N, int maxSoFar ) {
  if (N >= size)
    return maxSoFar;
  else if (nums[N] > maxSoFar)
    return max( N+1, nums[N] );
  else
    return max( N+1, maxSoFar );
  }

int main() {
  cout << max( 1, nums[0] );
  return 0;
  }
Variables: - (define x 3) int x; // declare x = 3; // assign // OR int x = 3; // initialize Compare with the tail-recursion of Scheme: The tail-recursion changes the argument each time through. The assembly version assigns to i each time through. Tail-recursion can be implemented as a loop + (re)assign parameters! Exercise: Modify this, so that you compute the sum of ---- (define vec (make-vector 50)) (fori= 0 (lambda (i) (< i (vector-length vec))) add1 (lambda (i) (vector-set! vec i i))) ---- const int size = 50; int vec[] = new int[size]; int i; for ( i = 0; i < size; i = i + 1 ) { vec[i] = i; } ; Using the fori= construct: ;(fori= 0 ; (index-checker vec) ; add1 ; (lambda (i) (vector-set! vec i i))) apr.03 advanced Jam: stack apr.06 implementing Jam apr.08 exam2 review hw e.c.: lab: optional: arithmetic hw: assign project apr.13 (catchup) apr.15 multi-dim arrays scheme, C lab: C apr.17 object-oriented I apr.20 object-oriented II lab: timing apr.22 halting prob apr.24 summary Labs: arithmetic imprecision; timing qsort, msort, isort; optional topics: eval, program-as-data, shcme-in-scheme max: scheme-accum C-accum scheme-for C-for Jam-for The Scheme-accumulator version of max

Scheme, accumulator version

;; max: int, double --> double
;; Return the largest value out of vec[i]...vec[size-1],max-so-far.
;; N.B. It is an error if vec does not have at least one element.
;;
(define (max i max-so-far)
  (if (>= i size)
      max-so-far
      (if (< max-so-far (vector-ref vec i))
          (max (add1 i) (vector-ref vec i))
          (max (add1 i) max-so-far))))

(max 1 (vector-ref vec 0))

C, accumulator version

#include 
const int size = 50;
int nums[size];

int max( int N, int maxSoFar ) {
  if (N >= size)
    return maxSoFar;
  else if (nums[N] > maxSoFar)
    return max( N+1, nums[N] );
  else
    return max( N+1, maxSoFar );
  }

int main() {
  cout << max( 1, nums[0] );
  return 0;
  }

C, for-loop version

//   max-for.cc
//   Compile this with "g++ -Wall max-for.cc".

#include 


const int size = 50;
double vec[size];


// max
// The "for" version in C.
//
double max( int i, double maxSoFar ) {
  if (i >= size) {
    return maxSoFar;
    }
  else if (maxSoFar < vec[i]) {
    return max( i+1, vec[i] );
    }
  else {
    return max( i+1, maxSoFar );
    }
    // N.B. In this case, the curly-bracces were optional,
    // since each if/else body had only one statement.
  }

int main() {
  cout << max( 1, vec[0] );
  return 0;
  }
;; max: . --> double ;; The Scheme fori= version. ;; Return the largest value out of vec[0]...vec[size-1] ;; N.B. It is an error if vec does not have at least one element. ;; (define (max) (local [(define max-so-far (vector-ref vec 0))] (begin (fori= 1 ; start (lambda (i) (>= i size)) ; stop? add1 ; next (lambda (i) ; body! (if (< max-so-far (vector-ref vec i)) (set! max-so-far (vector-ref vec i)) (void)))) max-so-far ; This is the 2nd stmt in the begin. ))) // The C-for version of max. // N.B. It is an error if vec does not have at least one element. // double max() { double maxSoFar = vec[0]; int i; for ( i = 1; i < size; i = i + 1 ) { if (maxSoFar < vec[i]) { maxSoFar = vec[i]; } } return maxSoFar; } ---- apr.08: tail-recursion to loops in JAM. JAM: vec <-- 100 i uno <-- 1 diff msf <-- ldx vec 0 i <-- 1 diff <-- size - i blez diff end ldx veci vec i diff <-- veci - msf blez diff endfor msf <-- veci endFor: i <-- i + 1 jmpi startFor end Notice now, how tail-recurion, in JAM, becomes "change the arguments (stored in registers), and do a jump". This is quick and easy. To think about: How would you translate function-call to JAM? Imagine I had a subroutine that location 5000, which took the sqrt of R0, and put the answer in R1 (trashing the other registers). How would you call it? What would happen at end of that routine? Would this work for recursive functions? --------