1 / 74

Functional Programming

Functional Programming. Universitatea Politehnica Bucuresti 2008-2009 Adina Magda Florea http://turing.cs.pub.ro/fp_09. Lecture No. 4 & 5. High-order procedures (continuation from Lecture 3) Iteration vs recursion Tail recursive calls Continuations The continuation chain

srinehart
Download Presentation

Functional Programming

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Functional Programming Universitatea Politehnica Bucuresti2008-2009 Adina Magda Florea http://turing.cs.pub.ro/fp_09

  2. Lecture No. 4 & 5 • High-order procedures (continuation from Lecture 3) • Iteration vs recursion • Tail recursive calls • Continuations • The continuation chain • Association lists • Vectors

  3. (Parenthesis on equality) (eqv? obj1 obj2)  • Compares two values to see if they refer to the same object • Returns #t if obj1 and obj2 should normally be regarded as the same object. • The eqv? procedure returns #t if: • obj1 and obj2 are both #t or both #f. or • obj1 and obj2 are both symbols and (string=? (symbol->string obj1)           (symbol->string obj2)) =>   #t

  4. (Parenthesis on equality) (eq? obj1 obj2) • eq? is similar to eqv? in most cases • eq? and eqv? are guaranteed to have the same behavior on symbols, booleans, the empty list, pairs, procedures, and non-empty strings and vectors. • eq?'s behavior on numbers and characters is implementation-dependent, but it will always return either true or false, and will return true only when eqv? would also return true. • eq? may also behave differently from eqv? on empty vectors and empty strings.

  5. (Parenthesis on equality) (equal? obj1 obj2) • does a deep, element-by-element structural comparison (eqv? '(a b c) '(a b c)) => #f (equal? '(a b c) '(a b c)) => #t (eqv? 'a 'a) => #t (eqv? 5 5) => #t (eqv? () ()) => #t (eqv? "" "") => #f (eqv? "aa" "aa") => #f (equal? "aa" "aa") => #t (eqv? 'abc 'abc) => #t

  6. 1. High-order procedures – returning functions (cont from Lect 3) • Have a procedure that can create procedures of some general type, producing a specialized procedure each time it's called. (define (make-mem-procpred?) … to obtainmember1(equivalent to member in Scheme) andmemq1(equivalent to memq in Scheme)

  7. High-order procedures – returning functions (define (make-mem-proc pred?) (letrec ((mem-proc (lambda (thing lis) (if (null? lis) #f (if (pred? (car lis) thing) lis (mem-proc thing (cdr lis))))))) mem-proc)) (define member1 (make-mem-procequal?)) (define memq1 (make-mem-proceq?)) (member1 'a '(a b c)) => (a b c) ((make-mem-proc ?equal) 'a '(a b c))) (member1 '(a) '((a) b c)) => ((a) b c) (member1 'b '(a b c)) => (b c) (member1 '(b) '(a (b) c)) => ((b) c) (memq1 'a '(a b c)) => (a b c) (memq1 '(b) '(a (b) c)) => #f

  8. High-order procedures – returning functions ;;; insert: given an ordering predicate, construct a procedure that ;;; adds a given value to a given ordered list, returning the new list ;;; (also ordered) ;;; Given: ;;; PRED?, a binary predicate ;;; Result: ;;; INSERTER, a binary procedure ;;; Precondition: ;;; PRED? expresses an ordering relation (that is, it is connected ;;; and transitive). ;;; Postcondition: ;;; Given any value NEW-ELEMENT that meets any precondition that ;;; PRED? imposes on its first argument, and any list LS of values ;;; that meet the preconditions that PRED? imposes on either of ;;; its arguments and moreover are ordered within LS by PRED?, ;;; INSERTER returns a list, also ordered by PRED?, containing all ;;; of the elements of LS and in addition NEW-ELEMENT.

  9. High-order procedures – returning functions (define insert (lambda (pred?) (letrec ((inserter (lambda (new-element lis) (cond ((null? lis) (list new-element)) ((pred? new-element (car lis)) (cons new-element lis)) (else (cons (car lis) (inserter new-element (cdr lis)))))))) inserter))) ((insert <) 2 '(1 3)) => (1 2 3) (define insert_less (insert <)) (insert_less 2 '(1 3)) ((insert >) 2 '(3 1)) => (3 2 1) ((insert string<?) "mm" '("aa" "bb" "xx")) => ("aa" "bb" "mm" "xx")

  10. High-order procedures – apply (apply proc arg1 … args) • proc must be a procedure and args must be a list. • Calls proc with the elements of the list (append (list arg1 ...) args) as the actual arguments. • args must be always present (apply + (list 3 4))   =>   7 (apply + 1 '(2 3))   =>   6 (apply min 5 1 3 '(6 8 3 2 5))   =>   1

  11. High-order procedures – apply (define first  (lambda (lis)    (apply (lambda (x . y) x)   lis))) (define rest  (lambda (lis)    (apply (lambda (x . y) y) lis))) (first '(a b c d))      =>   a (rest '(a b c d))      =>    (b c d) (define compose   (lambda (fg)     (lambda args       (f (apply g args))))) ((compose sqrt *) 12 75)   =>   30

  12. 2. Iteration vs recursion - Iteration (do((<variable1> <init1> <step1>)      ...)    (<test> <expression> ...)  <command> ...) (define (length1 list) (do ((len 0 (+ len 1)) ; start with LEN=0, increment (l list (rest l))) ; ... on each iteration ((null? l) len))) ; (until the end of the list)

  13. Iteration vs recursion - Iteration (do((<variable1> <init1> <step1>)      ...)    (<test> <expression> ...)  <command> ...) (let ((x '(1 3 5 7 9))) (do ((x x (cdr x))        (sum 0 (+ sum (car x))))       ((null? x) sum)))  => 25

  14. Iteration vs recursion - Iteration • Using dolist for iteration (dolist (x list) body ...) • Loop with 'x' bound to elements of 'list'. Language: Swindle (define (length2 list) (let ((len 0)) ; start with LEN=0 (dolist (element list) ; and on each iteration (set! len (+ len 1))) ; increment LEN by 1 len))

  15. Iteration vs recursion - Iteration • Using for-each for iteration (define (length3 list) (let ((len 0)) ; start with LEN=0 (for-each (lambda (x) (set! len (+ len 1))) list) ; and for each element ; increment LEN by 1 len)) ; and return LEN

  16. Iteration vs recursion – named let (let <variable> <bindings> <body>) • ``Named let'' is a variant on the syntax of let which provides a more general looping construct than do and may also be used to express recursions. • It has the same syntax and semantics as ordinary let except that <variable> is bound within <body> to a procedure whose formal arguments are the bound variables and whose body is <body>. • Thus the execution of <body> may be repeated by invoking the procedure named by <variable>.

  17. Iteration vs recursion – named let (let loop ((numbers '(3 -2 1 6 -5))            (nonneg '())            (neg '()))   (cond ((null? numbers) (list nonneg neg))         ((>= (car numbers) 0)          (loop (cdr numbers)                (cons (car numbers) nonneg)                neg))         ((< (car numbers) 0)          (loop (cdr numbers)                nonneg                (cons (car numbers) neg)))))    =>   ((6 1 3) (-5 -2))

  18. Iteration vs recursion - Recursion (define (length4 lis) (cond ((null? lis) 0) (else (+ 1 (length4 (cdr lis)))))) • Scheme's procedure-calling mechanism supports efficient tail-recursive programming, where recursion is used instead of iteration. • The above example is NOT tail-recursive!

  19. 3. Tail-recursive programming • When a procedure calls itself in a way that is equivalent to iterating a loop, Scheme automatically "optimizes" it so that it doesn't need extra stack space. • You can use recursion anywhere you could use a loop in a conventional language. • The basic idea is that you never have to return to a procedure if all that procedure will do is return the same value to its caller.

  20. Tail-recursive programming • Whether a procedure is recursive or not, the calls it makes can be classified as subproblems or reductions • If the last thing a procedure does is to call another procedure, that's known as a reduction - the work being done by the caller is complete, because it "reduces to" the work being done by the callee. (define (foo) (bar) (baz))

  21. Tail-recursive programming (define (foo) (bar) (baz)) • When foo is called, it does two things: it calls bar and then calls baz. • After the call to bar, control must return to foo, so that it can continue and call baz. • The call to bar is therefore a subproblem - a step in the overall plan of executing foo. • When foo calls baz, however, that's all it needs to do - all of its other work is done. • The result of the call to foo is just the result of foo's call to baz.

  22. Tail-recursive programming (define (foo) (bar) (baz)) • There's really no need to do two returns, passing through foo on the way back. • Instead, Scheme avoids saving foo's state before the call to baz, so that baz can return directly to foo's caller, without actually coming back to foo. • Tail-calling allows recursion to be used for looping, because a tail call doesn't save the caller's state on a stack.

  23. Tail-recursive programming (define (length4 lis) (cond ((null? lis) 0) (else (+ 1 (length4 (cdr lis)))))) • Why this is not tail-recursive? • How can we make it tail-recursive?

  24. Tail-recursive programming (define (length5 list) (length5-aux list 0)) (define (length5-aux sublist len-so-far) (if (null? sublist) len-so-far (length5-aux (rest sublist) (+ 1 len-so-far))))

  25. Tail-recursive programming • The recursive definition n! = n × (n - 1)!, where 0! is defined to be 1. (define factorial  (lambda (n)    (let fact ((i n))      (if (= i 0)          1          (* i (fact (- i 1))))))) (factorial 0)   =>1(factorial 1)  => 1(factorial 2)   =>2(factorial 3)   =>6(factorial 10)  => 3628800

  26. Tail-recursive programming • Employs the iterative definition n! = n × (n - 1) × (n - 2) × ... × 1, using an accumulator, a, to hold the intermediate products. (define factorial  (lambda (n)    (let fact ((i n) (a 1))      (if (= i 0)          a          (fact (- i 1) (* a i))))))

  27. Tail-recursive programming 0, 1, 1, 2, 3, 5, 8, etc., (define fibonacci  (lambda (n)    (let fib ((i n))      (cond        ((= i 0) 0)        ((= i 1) 1)        (else (+ (fib (- i 1)) (fib (- i 2)))))))) (fibonacci 0)    =>0(fibonacci 1)    =>1(fibonacci 2)    =>1(fibonacci 3)    =>2(fibonacci 20)    =>6765(fibonacci 30)    =>832040

  28. Tail-recursive programming • A more efficient solution is to adapt the accumulator solution of the factorial example above to use two accumulators, a1 for the current Fibonacci number and a2 for the preceding one. (define fibonacci  (lambda (n)    (if (= n 0)        0        (let fib ((i n) (a1 1) (a2 0))          (if (= i 1)              a1              (fib (- i 1) (+ a1 a2) a1))))))

  29. Tail-recursive programming | (fib 5 1 0)| (fib 4 1 1)| (fib 3 2 1)| (fib 2 3 2)| (fib 1 5 3)| 5 | (fib 5)|  (fib 4)|  |(fib 3)|  | (fib 2)|  | |(fib 1)|  | |1|  | |(fib 0)|  | |0|  | 1|  | (fib 1)|  | 1|  |2|  |(fib 2)|  | (fib 1)|  | 1|  | (fib 0)|  | 0|  |1|  3| (fib 3)| | (fib 2)| | (fib 1)| | 1| | (fib 0)| | 0| | 1| | (fib 1)| | 1| 2|5

  30. Tail-recursive programming (define divisors  (lambda (n)    (let f ((i 2))      (cond        ((>= i n) '())        ((integer? (/ n i))         (cons i (f (+ i 1))))        (else (f (+ i 1))))))) (divisors 100) => (2 4 5 10 20 25 50) (divisors 5) => () Is non-tail-recursive when a divisor is found and tail-recursive when a divisor is not found.

  31. Tail-recursive programming (define divisors  (lambda (n)    (let f ((i 2) (ls '()))      (cond        ((>= i n) ls)        ((integer? (/ n i))         (f (+ i 1) (cons i ls)))        (else (f (+ i 1) ls)))))) • This version is fully tail-recursive. • It builds up the list in reverse order, but this is easy to remedy, either by reversing the list on exit or by starting at n - 1 and counting down to 1.

  32. 4. Continuations • During the evaluation of a Scheme expression, the implementation must keep track of two things: • what to evaluate and • what to do with the value. (if (null? x) (quote ()) (cdr x)) • "what to do with the value" is the continuation of a computation. • At any point during the evaluation of any expression, there is a continuation ready to complete, or at least continue, the computation from that point.

  33. Continuations • Let's assume that x has the value (a b c). • Then we can isolate six continuations during the evaluation of (if (null? x) (quote ()) (cdr x)), the continuations waiting for: • the value of (if (null? x) (quote ()) (cdr x)), • the value of (null? x), • the value of null?, • the value of x, • the value of cdr, and • the value of x (again).

  34. Call-with-current-continuation • Scheme allows the continuation of any expression to be obtained with the procedure call-with-current-continuation, which may be abbreviated call/cc in most implementations. • call/cc must be passed a procedure p of one argument. • call/cc obtains the current continuation and passes it to p. • The continuation itself is represented by a procedure k. (call/cc  (lambda (k)    (* 5 (k 4))))

  35. Call-with-current-continuation (call/cc  (lambda (k)    (* 5 (k 4))))     => 4 • Each time k is applied to a value, it returns the value to the continuation of the call/cc application. • This value becomes, in essence, the value of the application of call/cc. • If p returns without invoking k, the value returned by the procedure becomes the value of the application of call/cc.

  36. Call-with-current-continuation (call/cc  (lambda (k)    (* 5 4)))    => 20 (call/cc  (lambda (k)    (* 5 (k 4))))    => 4 (+ 2   (call/cc     (lambda (k)       (* 5 (k 4)))))    => 6

  37. call/cc to provide a nonlocal exit from a recursion (define product  (lambda (lis)    (call/cc      (lambda (break)        (let f ((lis lis))          (cond            ((null? lis) 1)            ((= (car lis) 0) (break 0))            (else (* (car ls) (f (cdr lis)))))))))) (product '(1 2 3 4 5))    => 120(product '(7 3 8 0 1 9 5))    => 0

  38. Another example with call/cc let ((x (call/cc (lambda (k) k))))  (x (lambda (ignore) "hi")))    => "hi" • The continuation may be described as: • Take the value, bind it to x, and apply the value of x to the value of (lambda (ignore) "hi") • (lambda (k) k) returns its argument =>x is bound to the continuation itself • This continuation is applied to the procedure resulting from the evaluation of (lambda (ignore) "hi") • Effect = binding x (again!) to this procedure and applying the procedure to itself. • The procedure ignores its argument and returns "hi".

  39. Factorial • Saves the continuation at the base of the recursion before returning 1, by assigning the top-level variable retry. (define retry #f)(define factorial  (lambda (x)    (if (= x 0)        (call/cc (lambda (k) (set! retry k) 1))        (* x (factorial (- x 1)))))) • With this definition, factorial works as we expect factorial to work, except it has the side effect of assigning retry. (factorial 4)    => 24 (retry 1)    => 24 • The continuation bound to retry = "Multiply the value by 1, then multiply this result by 2, then multiply this result by 3, then multiply this result by 4." • If we pass the continuation a different value, i.e., not 1, we will cause the base value to be something other than 1 and hence change the end result. (retry 2)    => 48 (retry 5)    => ?? 5

  40. (define retry #f) (define factorial  (lambda (x)    (if (= x 0)        (call/cc (lambda (k) (set! retry k) 1))        (* x (factorial (- x 1)))))) (factorial 3)    => 6 (retry 1)    => 6 (retry 2)    => 12 (retry 3)    => ?? (call/cc (lambda (k) (set! retry k) 1)) (retry 1) => ?? (retry 2) => ??

  41. Continuations - summary • call-with-current-continuation is a procedure of exactly one argument, p, which is another procedure k to execute after the current continuation has been captured. • The procedure p - abortable procedure. • The current continuation is passed to that procedure p, which can use it (or not) as it pleases. • The captured continuation is itself packaged up as a procedure k, also of one argument. • The procedure k - escape procedure. • The abortable procedure'sargument is the escape procedure that encapsulates the captured continuation.

  42. Continuations - summary • call-with-current-continuation does the following: • Creates the escape procedure k that captures the current continuation. • If called, this procedure will restore the continuation at the point of call to call-with-current-continuation. • Calls the procedure passed as its argument, handing it the escape procedure as its argument. • Continuations are resumed by calling the escape procedure. • When the escape procedure is called: • it abandons whatever computation is going on, • it restores the saved continuation, • and resumes executing the saved computation at the point where call-with-current-continuation occurred

  43. Continuations - summary • The current continuation at any point in the execution of a program is an abstraction of the rest of the program. Thus in the program (+ 1 (call/cc (lambda (k) (+ 2 (k 3))))) • the rest of the program, from the point of view of the call/cc-application, is the following program-with-a-hole (with [] representing the hole): (+ 1 [])

  44. Continuations - summary • The current continuation at any point in the execution of a program is an abstraction of the rest of the program. Thus in the program (+ 1 (call/cc (lambda (k) (+ 2 (k 3))))) • the rest of the program, from the point of view of the call/cc-application, is the following program-with-a-hole (with [] representing the hole): (+ 1 [])

  45. Continuations - summary (+ 1 (call/cc (lambda (k) (+ 2 (k 3))))) => 4 (define r #f) (+ 1 (call/cc (lambda (k) (set! r k) (+ 2 (k 3))))) => 4 (r 5) => 6 (+ 3 (r 5)) => 6

  46. 5. The continuation chain • In most conventional language implementations calling a procedure allocates an activation record (or "stack frame") that holds the return address for the call and the variable bindings for the procedure being called. • The stack is a contiguous area of memory, and pushing an activation record onto the stack is done by incrementing a pointer into this contiguous area by the size of the stack frame. • Removing a stack frame is done by decrementing the pointer by the size of the stack frame.

  47. The continuation chain • Scheme implementations are quite different. • Variable bindings are not allocated in a stack, but instead in environment frames on the garbage-collected heap. • This is necessary so that closures can have indefinite extent, and can count on the environments they use living as long as is necessary. • The garbage collector will eventually reclaim the space for variable bindings in frames that aren't captured by closures.

  48. The continuation chain • Most Scheme implementations also differ from conventional language implementations in how they represent the saved state of callers. • In a conventional language implementation, the callers' state is in two places: • the variable bindings are in the callers' own stack frames • the return address is stored in the callee's stack frame.

  49. The continuation chain • In most Scheme implementations, the caller's state is saved in a record on the garbage-collected heap, called a partial continuation. • It's called a continuation because it says how to resume the caller when we return into it--i.e., how to continue the computation when control returns. • It's called a partial continuation because that record, by itself, it only tells us how to resume the caller, not the caller's caller or the caller's caller's caller.

More Related