;;; Higher-Order Procedures ;;; Summary of lectures from Programming Languages ;;; Evening section: Sept 19, 2001 ;;; Morning section: Sept 24, 2001 ;;; Stewart M. Clamen ;; Some preliminary definitions (define first car) (define rest cdr) ;;; PROCEDURAL ABSTRACTION ;; Why do we have procedures? What purpose do they serve? Procedures ;; allow us to apply a common functionality repeatedly to a variety of ;; values. Consider: ;; Suppose we want to determine if the value 4 is even?. We could ;; evaluate (= (remainder 4 2) 0) ;; and find out. Now suppose we wanted to know if 11 is even? (= (remainder 11 2) 0) ;; This could get repetitive very quickly. In the context of a larger ;; program, all this extra typing would make the program verbose and ;; hard to read. Procedures allow us to abstract common ;; functionality. (define (even? n) (= (remainder n 2) 0)) ;;; PROCEDURES AS ARGUMENTS ;; Consider the following procedures: INCREMENT-EACH, SQUARE-EACH, and ;; RECIPROCATE-EACH talk a list as an argument and return a list of ;; the same length with each element incremented, squared, or ;; reciprocated, respectively. (define (increment n) (+ n 1)) (define (increment-each ls) (if (null? ls) '() (cons (increment (first ls)) (increment-each (rest ls))))) (define (square n) (* n n)) (define (square-each ls) (if (null? ls) '() (cons (square (first ls)) (square-each (rest ls))))) (define (reciprocate-each ls) (if (null? ls) '() (cons (/ (first ls)) (reciprocate-each (rest ls))))) ;; The "shape" of the three *-EACH procedures is the same. Except for ;; the procedure that is applied to each element in the (supplied) ;; list, they are syntactically the same. Scheme let's us turn ;; that procedure into an argument! Consider: (define (for-each f ls) (if (null? ls) '() (cons (f (first ls)) (for-each f (rest ls))))) ;; We have replaced the procedure with the argument "f". We can ;; define the three *-EACH procedures in terms of FOR-EACH. (define (increment-each ls) (for-each increment ls)) (define (square-each ls) (for-each square ls)) (define (reciprocate-each ls) (for-each / ls)) ;; Unlike many other programming languages, Scheme treats procedures ;; as first-class values. Like numbers, pairs, lists, etc. procedural ;; values can be passed as arguments to procedures, and even returned ;; from procedures. ;; That is why the alternative syntax for defining a named procedure ;; is the same as defining any other value. ;; NOTE: (define a (list 1 2 3)) (define square (lambda (n) (* n n))) ;; LAMBDA is the Scheme special form for creating a procedural value. ;; It takes two "arguments": an argument list, and a body. ;;; PROCEDURES AS RETURN VALUES ;; Consider the following simple procedures. (define add2 (lambda (n) (+ n 2)) (define sub2 (lambda (n) (- n 2)) (define double (lambda (n) (* n 2)) (define half (lambda (n) (/ n 2)) (define square (lambda (n) (expt n 2)) ;; They all apply a different procedure, with their argument and the ;; value 2. We could abstract the pattern as we did with FOR-EACH ;; above. (define apply-with-2 (lambda (f n) (f n 2)) (define add2 (lambda (n) (apply-with-2 + n))) (define sub2 (lambda (n) (apply-with-2 - n))) ;; As an alternative, however, we could create a procedure that ;; creates unary (i.e., single-argument) procedures such as HALF and ;; ADD2 above. (define make-apply-to-2 (lambda (f) (lambda (n) (f n 2)))) (define double (make-apply-to-2 *)) (define half (make-apply-to-2 /)) (define square (make-apply-to-2 expt)) ;; This syntax might seem confusing. But if we expand the application ;; of MAKE-APPLY-TO-2, substituting the argument value for "f", we see ;; that it is the same as our original definitions (of DOUBLE, HALF, ;; SQUARE) earlier. ;; The ability for procedures to return new procedures is a very ;; powerful mechanism that allows us to write highly abstract ;; procedures. Some examples: ;; COMPOSE: compose two procedures into a new procedure (define (compose f g) (lambda (x) (f (g x)))) ;;; LET and LAMBDA ;; In previous lectures, we have seen how the special form LET can be ;; used to introduce new (albeit temporary) variable bindings: (let ((a 10) (b 12)) (+ a b)) ; ==> 22 ;; New variable bindings are also introduced when a procedure is ;; applied: (define add (lambda (a b) (+ a b))) (add 10 12) ; ==> 22 ;; In fact, LET and LAMBDA are two sides of the same coin. Every LET ;; expression can be rewritten in terms of LAMBDA: ;; Substituting in the (procedural) value of ADD, we get: ((lambda (a b) (+ a b)) 10 12) ;; This is no different in meaning from the LET expression above ;;; PROCEDURES AND ENVIRONMENTS ;; Let's return to our MAKE-APPLY-TO-2 procedure from earlier, and our ;; definition of DOUBLE in terms of it (define make-apply-to-2 (lambda (f) (lambda (n) (f n 2)))) (define double (make-apply-to-2 *)) ;; Acknowledging the LET/LAMBDA equivalence, we could also have ;; defined DOUBLE in this way: (define double (let ((f *)) (lambda (n) (f n 2)))) (square 10) ; ==> 100 ;;; Procedure Environments ;; Earlier we saw how the LAMBDA special form has two parts: an ;; argument list ("(n)" in this case) and a body ("(f n 2)" in this ;; case). When the procedure DOUBLE is applied (passed a value for ;; the argument n) a new binding is made for n ("10" in this case). ;; Simply expanding (square 10) two "levels", we get: ;; (square 10) ;; ((lambda (n) (f n 2)) 10) ;; (f 10 2) ;; How do we substitute "f"? Where is the binding for "f"? Looking ;; back at the definition for DOUBLE, we see that the LET introduced a ;; binding for "f". ;; A procedure in Scheme, the result of evaluation a LAMBDA, is ;; more than a function (description of behavior), it also includes a ;; binding context, an environment. The environment is the chain of ;; binding "frames" created by each LET or applied procedure that is ;; active when the lambda was evaluated, up to the top level (where ;; the user prompt is). (define double ; procedure = function + environment (let ((f *)) ; environment frame (lambda (n) (f n 2)))) ; function