324 Lecture Week 5 ================== Closures / Local varables ------------------------- [notes to come] Variable Assignment: set! ------------------------- Although many programs can be written without them, assignments to top-level variables or let-bound and lambda-bound variables are sometimes useful. - assignments do not create new bindings, as with let or lambda, - assignments rather change the values of existing bindings. Assignments are performed with set!. Recall convention: procedures and syntactic forms that have side-effects end with ! (except input and output procedures) (define x 1) x => 1 (set! x '(2)) x => (2) (let ((x '(a b c))) (set! x (reverse x)) x) => (c b a) Most of the assignments that are either necessary or convenient in other languages are both unnecessary and inconvenient in Scheme, since there is typically a clearer way to express the same algorithm without assignments. One use of assignments in Scheme is to count the number of times a procedures is called: (define cons-count 0) (define cons (let ((old-cons cons)) (lambda (x y) (set! cons-count (+ cons-count 1)) (old-cons x y)))) (cons 'a '(b c)) (a b c) cons-count => 1 (cons 'a (cons 'b (cons 'c '()))) (a b c) cons-count => 4 Assignments frequently used to implement procedures that must maintain some internal state. Example: Construct procedure that returns 0 first time it's called, 1 the second time, 2 the third time, etc. (define next 0) (define count (lambda () (let ((v next)) (set! next (+ next 1)) v))) Problem: the variable next is visible at top level. Solution: define next inside the count procedure. (define count (let ((next 0)) (lambda () (let ((v next)) (set! next (+ next 1)) v)))) Benefit: this solution easily generalizes to provide multiple counters, each with its own local value. (define make-counter (lambda () (let ((next 0)) (lambda () (let ((v next)) (set! next (+ next 1)) v))))) (define count1 (make-counter)) (define count2 (make-counter)) Notice that next is bound instide of make-counter but outside the procedure returned by make-counter. Tutorial exercise: create a stack data-object set-car! and set-cdr! --------------------- In addition to changing the values of variables, we can also change the values of the car and cdr fields of a pair, using the procedures set-car! and set-cdr!. (define p (list 1 2 3)) (set-car! (cdr p) 'two) p => (1 two 3) (set-cdr! p '()) p => (1) Exercise: extend this stack to a queue object. (We need to use a header to keep track of the front and end of the list.) Iteration ========= Basic construct: begin (begin exp1 exp2 ... expn) Executes expressions in order, particularly for side-effects of exp1... expn and/or value of expn. Let's write sum-of-squares recursively. ; 1^2 + 2^2 + ... + n^2 (define sum-of-squares (lambda (n) (if (= n 0) 0 (+ (* n n) (sum-of-squares (- n 1)))))) Let's write it in a more iterative style, accumulating the result in a variable. (define sum-of-squares (lambda (n) (let ((s 0)) (letrec ((s-o-s (lambda (i) (if (> i n) s (begin (set! s (+ s (* i i))) (s-o-s (+ i 1))))))) (s-o-s 0))))) Exercise: write begin (can't! need macros) Exercise: review the 148/150 slides on turning iteration into recursion. Exercise: write sum-of-squares so that i and s are both arguments in the recursive call. While ----- To emulate a while loop with condition c and body statements s ...: (letrec ((loop (lambda () (if c (begin s ... (loop)))))) (loop)) We'd rather write this as: (while condition body ...) We can't write a procedure 'while', since we don't want the parameters automatically evaluated, once, at the beginning. Instead, we make a new syntactic form: (define-syntax while (syntax-rules () ((while condition body ...) (letrec ((loop (lambda () (if condition (begin body ... (loop)))))) (loop))))) Now we can write sum-of-squares as: (define sum-of-squares (lambda (n) (let ((i 1) (s 0)) (while (<= i n) (set! s (+ s (* i i))) (set! i (+ i 1))) s))) Extending Scheme syntax: macros =============================== Syntactic extensions are defined with define-syntax. Macros extend the language by conceptually extending the compiler -- telling the parser how to recognize a new construct, and how to generate code for the new construct. Let's review macros in C/C++. They are essentially textual substitutions. #define SQUARE(x) x*x This has a problem: 4 * SQUARE(2 + 3) gets expanded to 4 * 2 + 3 * 2 + 3, completely messing up the intended meaning. To fix SQUARE: #define SQUARE(x) ((x)*(x)) This is not a problem in Scheme, since all expressions are fully-parenthesized. One can go as far as (I saw this at one company I worked for): #define BEGIN { #define END } Standard macros in Scheme don't allow manipulation below the level of lists of tokens. But some extensions do. Consider now a macro to swap the values of two variables: #define SWAP(v1,v2) int t = v1; v1 = v2; v2 = t; This can't be a procedure in Java/Python/Scheme because it needs the parameters as names of variables, not (just) their values. This has a problem if t is one of the variables we want to swap: SWAP(t,v): int t = t; t = v; v = t; In Scheme: (define-syntax swap (syntax-rules () ((swap v1 v2) (let ((t v1)) (set! v1 v2) (set! v2 t))))) The syntax-rules system of macros automatically avoids unintentional variable capture (aliasing). It treats (swap t v) to something like: (let ((t-new t)) (set! t v) (set! t t-new))))) let and set! are names defined in the environment where swap is defined. v1 and v2 get their meaning from the call to swap. Much like in procedures, all other names are considered local to the macro. define-syntax ------------- define-syntax is similar to define, except that define-syntax associates a syntactic transformation procedure, or transformer, with a keyword (such as let), rather than associating a value with a variable. Here is how we might define let with define-syntax. (define-syntax let (syntax-rules () ((_ ((x v) ...) e1 e2 ...) ((lambda (x ...) e1 e2 ...) v ...)))) The identifier appearing after define-syntax is the name, or keyword, of the syntactic extension being defined, in this case let. The syntax-rules form is an expression that evaluates to a transformer. - The item following syntax-rules is a list of auxiliary keywords and is nearly always (). An example of an auxiliary keyword is the else of cond. - Following the list of auxiliary keywords is a sequence of one or more rules, or pattern/template pairs. Only one rule appears in our definition of let. The pattern part of a rule specifies the form that the input must take, and the template specifies to what the input should be transformed. The pattern should always be a structured expression whose first element is an underscore ( _ ). - If more than one rule is present, the appropriate one is chosen by matching the patterns, in order, against the input during expansion. An error is signaled if none of the patterns match the input. Identifiers appearing within a pattern are _pattern variables_, unless they are listed as auxiliary keywords. Pattern variables match any substructure and are bound to that substructure within the corresponding template. The notation "pat ..." in the pattern allows for zero or more expressions matching the ellipsis prototype pat in the input. The number of pats in the input determines the number of exps in the output: any ellipsis prototype in the template must contain at least one pattern variable from an ellipsis prototype in the pattern. and / or -------- The definition of and below is somewhat more complex than the one for let. (define-syntax and (syntax-rules () ((_) #t) ((_ e) e) ((_ e1 e2 e3 ...) (if e1 (and e2 e3 ...) #f)))) The definition of or below is similar to the one for and except that a temporary variable must be introduced for each intermediate value so that we can both test the value and return it if it is a true value (we only want to evaluate an expression once!). (A similar temporary is not needed for and since there is only one false value, #f.) (define-syntax or (syntax-rules () ((_) #f) ((_ e) e) ((_ e1 e2 e3 ...) (let ((t e1)) (if t t (or e2 e3 ...)))))) for --- Let's make one more macro, essentially Python's for. We want (for v in l body ...) to mean (for-each (lambda (v) body ...) l) Notice that we want to match "in" literally, not have it stand for an arbitrary third expression. So we put it in the list after syntax-rules. "for" is also a keyword, but define-syntax already knows this since it's what we're defining. In fact, mentioning it again at the beginning of the pattern is not necessary, so many people just put "_" as a dummy expression. (define-syntax for (syntax-rules (in) ((_ v in l body ...) (for-each (lambda (v) body ...) l)))) We can have multiple patterns. Suppose we also want a form: (for v from n to m body ...) (define-syntax for (syntax-rules (in from to) ((_ v in l body ...) (for-each (lambda (v) body ...) l)) ((_ v from n to m body ...) (let ((v n)) (while (<= v m) body ... (set! v (+ v 1))))))) Syntax-rules is just one system for specifying macros. Can a macro expansion accidentally refer to the "wrong" variable (with the same name in another scope)? Fortunately, no! Scheme actually keeps track of which variables are introduced by different applications of macros, and keeps them distinct. -> Scheme macros are said to be hygienic; what that really means is that they respect lexical scope.