Chapter 3 : Going Further (and more)
Primary resource: The Scheme Programming Language, by Dybvig.
Additional reference sources: Compilers: Principles, Techniques and Tools, by Aho, Sethi and Ullman. Scheme and the Art of Programming, by Springer and Friedman.
Recursion and let bindings
What will happen when you execute this expression?
(let ((last (lambda (ls) (if (null? (cdr ls)) (car ls) (last (cdr ls)))) )) (last '(3 4 6 1)) )
Problem: recursive call appears before binding is complete.
Solution alternatives:
Flat versus deep recursion
The above is example of flat recursion
Flat recursion:
Deep recursion:
Example of deep recursion: count-all
(define count-all (lambda (ls) (cond ((null? ls) 0) ((pair? (car ls)) (+ (count-all (car ls)) (count-all (cdr ls)))) (else (+ 1 (count-all (cdr ls)))) )))
Recall definition of first-class object (see end of notes for chapter 7).
Such an object can be:
Scheme procedures are first class.
Above is example of assigning procedure to a variable.
Following are examples of the other two.
Passing procedures as parameters
Example is the
map procedure
Takes two parameters: a procedure (of one parameter) and a list
Its result is the list after the procedure has been applied to all members
Code:
(define map (lambda (proc ls) (if (null? ls) '() (cons (proc (car ls)) (map proc (cdr ls))) ) ))
Informal explanation:
apply proc to car of list, recursively map proc to cdr of list, then apply cons to result of the two.
Example use: (map (lambda (n) (+ 1 n)) '(1 2 3 4))
What is result?
Map is a recursive procedure. Is this flat recursion, or deep recursion?
Returning a procedure as procedure value
Example is currying
Currying: writing a two parameter procedure as a one parameter procedure tha returns a one parameter procedure. Can generalize to more than two params.
Named after logician Haskell Curry
Usefulness: hold one parameter constant while varying the other
Simple example:
(define curried+ (lambda (m) (lambda (n) (+ m n))))
Example use:
(define add5 (curried+ 5)) (define add8 (curried+ 8)) (add5 7)
An example that demonstrates all three: compose
Procedure compose takes two procedures as parameters and returns another procedure that is a composition of the two.
Code:
(define compose (lambda (f g) (lambda (x) (f (g x)))))
The value of compose is: (lambda (x) (f (g x)))
Examples of its use:
(define h (compose sqrt add1))
(define k (compose add1 sqrt))
(h 8)
What is the result?
(k 9)
What is the result?
Higher-order Procedures
Definition depends on information resource; here is a sampling:
Named Let
a. global version: (already studied)
(define factorial (lambda (n) (if (= n 0) 1 (* n (factorial (- n 1))) )))
b. letrec version, restricted scope:
(letrec ((factorial (lambda (n) (if (= n 0) 1 (* n (factorial (- n 1))) )) )) (factorial 5) )
c. named let version:
(define factorial (lambda (n) (let fact ((i n)) (if (= i 0) 1 (* i (fact (- i 1))) ) )))
d. named let, with local variable a (accumulator) to hold intermediate products
(define factorial (lambda (n) (let fact ((i n) (a 1)) (if (= i 0) a (fact (- i 1) (* a i)) ) )))
Factorial in part d defined by n*(n-1)*(n-2)* ... * 1, rather than n * (n-1)!
Fibonacci
Lazy Evaluation, Pass-by-name, and Thunk!
What do these terms mean, and what do they have in common?
Lazy Evaluation: computation is evaluated only once, and not until needed.
Pass-by-name:
Thunk: a parameterless procedure used to implement both of the above.
Q: What do lazy evaluation and pass-by-name have in common?
A: In both cases, expressions are not evaluated until needed.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Pass by Name
- argument is not evaluated at the time of the call!
- evaluated only when encountered during execution of the called procedure.
- evaluation based on environment of calling procedure
- thunk used to evaluate argument, has access link to calling environment
- Example:
float doublit(float val) // imagine pass-by-name { return val * 2.0; }
example call, using pass by name:
a = doublit(c + 3.2 * b);
the function body becomes:
return (c + 3.2*b)*2.0;
- parentheses added to assure integrity of argument
- thunk is used to get values of b and c.
- Another Example:
void swap(int x, y) // imagine this is pass by name { int temp; temp = x; x = y; y = temp; }
Now, call it with:
swap(i, a[i]); where a is an array of integers.Under pass by name, the function body becomes:
temp = i;
i = a[i];
a[i] = temp;
when the l-value for
a[i] is computed in the third statement, i has changed!
- Macro expansion, such as in C or C++, has same limitation, e.g.
#define a i
#define b a[i]
. . .
temp = a;
a = b;
b = temp;
. . .
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Lazy Evaluation
For example, see pp 43-44 of Dybvig.
Procedure
lazy- takes thunk (parameterless procedure) as parameter
- maintains two local variables,
val and flag.-
val: will be set to value of thunk, the first time lazy is applied.-
flag: will be set to #t, the first time lazy is applied.- first time
lazy is applied, thunk is evaluated, value is saved in val and returned- at each subsequent application, value saved in
val is returned.
Syntactic Extension
Simple Example:
(reference-library "synrule.ss" "standard") (define-syntax tribe (syntax-rules () ((_) "wahoo") ; first rule, usage is (tribe) ((_ e) (+ e 1)) ; usage (tribe exp) ((_ e1 e2) (* e1 e2)) ; usage (tribe exp1 exp2) ))
Another Example:
; Primitive loop form. Use with 2 expressions: #iterations, and ; expression to evaluate. Will repeatedly evaluate it. (reference-library "synrule.ss" "standard") (define-syntax loop (syntax-rules () ((_ e1 e2) (let rec-loop ((i e1)) (if (<= i 1) e2 (begin e2 (rec-loop (- i 1))) )) )))
Pages 51 and 52 show examples for
let and and. Use ellipses (...) to indicate variable number of expressions.
Box-and-Pointer Representation
Will cover this in conjunction with textbook chapter 8 on pointers.
Continuations
Will cover this in conjunction with textbook chapter 12 on concurrency.