Presentation is loading. Please wait.

Presentation is loading. Please wait.

Functional Programming

Similar presentations


Presentation on theme: "Functional Programming"— Presentation transcript:

1 Functional Programming
Universitatea Politehnica Bucuresti Adina Magda Florea

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-proc pred?) … to obtain member1 (equivalent to member in Scheme) and memq1 (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-proc equal?)) (define memq1 (make-mem-proc eq?)) (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 '( ))   =>   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 (f g)     (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) Do is an iteration construct. It specifies a set of variables to be bound, how they are to be initialized at the start, and how they are to be updated on each iteration. When a termination condition is met, the loop exits after evaluating the <expression>s. Do expressions are evaluated as follows: The <init> expressions are evaluated (in some unspecified order), the <variable>s are bound to fresh locations, the results of the <init> expressions are stored in the bindings of the <variable>s, and then the iteration phase begins. Each iteration begins by evaluating <test>; if the result is false then the <command> expressions are evaluated in order for effect, the <step> expressions are evaluated in some unspecified order, the <variable>s are bound to fresh locations, the results of the <step>s are stored in the bindings of the <variable>s, and the next iteration begins. If <test> evaluates to a true value, then the <expression>s are evaluated from left to right and the value(s) of the last <expression> is(are) returned. If no <expression>s are present, then the value of the do expression is unspecified. The region of the binding of a <variable> consists of the entire do expression except for the <init>s. It is an error for a <variable> to appear more than once in the list of do variables.

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 Do is an iteration construct. It specifies a set of variables to be bound, how they are to be initialized at the start, and how they are to be updated on each iteration. When a termination condition is met, the loop exits after evaluating the <expression>s. Do expressions are evaluated as follows: The <init> expressions are evaluated (in some unspecified order), the <variable>s are bound to fresh locations, the results of the <init> expressions are stored in the bindings of the <variable>s, and then the iteration phase begins. Each iteration begins by evaluating <test>; if the result is false then the <command> expressions are evaluated in order for effect, the <step> expressions are evaluated in some unspecified order, the <variable>s are bound to fresh locations, the results of the <step>s are stored in the bindings of the <variable>s, and the next iteration begins. If <test> evaluates to a true value, then the <expression>s are evaluated from left to right and the value(s) of the last <expression> is(are) returned. If no <expression>s are present, then the value of the do expression is unspecified. The region of the binding of a <variable> consists of the entire do expression except for the <init>s. It is an error for a <variable> to appear more than once in the list of do variables.

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)                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)  => 

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
(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 0, 1, 1, 2, 3, 5, 8, etc., This solution requires the computation of the two preceding Fibonacci numbers at each step and hence is doubly recursive. For example, to compute (fibonacci 4) requires the computation of both (fib 3) and (fib 2), to compute (fib 3) requires computing both (fib 2) and (fib 1), and to compute (fib 2) requires computing both (fib 1) and (fib 0). This is very inefficient, and it becomes more inefficient as n grows.

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)))))) Here, zero is treated as a special case, since there is no preceding value. This allows us to use the single base case (= i 1). The time it takes to compute the nth Fibonacci number using this iterative solution grows linearly with n, which makes a significant difference when compared to the doubly recursive version. To get a feel for the difference, try computing (fibonacci 30) and (fibonacci 35) using both definitions to see how long each takes.

29 Tail-recursive programming
| (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 | (fib 5 1 0) | (fib 4 1 1) | (fib 3 2 1) | (fib 2 3 2) | (fib 1 5 3) | 5 Here, zero is treated as a special case, since there is no preceding value. This allows us to use the single base case (= i 1). The time it takes to compute the nth Fibonacci number using this iterative solution grows linearly with n, which makes a significant difference when compared to the doubly recursive version. To get a feel for the difference, try computing (fibonacci 30) and (fibonacci 35) using both definitions to see how long each takes.

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) => ( ) (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. The implementation must first evaluate (null? x) and, based on its value, evaluate either (quote ()) or (cdr x). "What to evaluate" is (null? x), and "what to do with the value" is to make the decision which of (quote ()) and (cdr x) to evaluate and do so.

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 In the first example, the continuation is obtained and bound to k, but k is never used, so the value is simply the product of 5 and 4. In the second, the continuation is invoked before the multiplication, so the value is the value passed to the continuation, 4. In the third, the continuation includes the addition by 2; thus, the value is the value passed to the continuation, 4, plus 2.

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 The nonlocal exit allows product to return immediately, without performing the pending multiplications, when a zero value is detected.

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 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.

42

43 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.

44 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.

45 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.

46 The continuation chain
On the other hand, each partial continuation holds a pointer to the partial continuation for its caller, so a chain of continuations represents how to continue the whole computation: how to resume the caller, and when it returns, how to resume its caller, and so on until the computation is complete. This chain is therefore called a full continuation.

47 The continuation chain
In most Scheme implementations, a special register called the continuation register is used to hold the pointer to the partial continuation for the caller of the currently-executing procedure. When we call a procedure, we can package up the state of the caller as a record on the heap (a partial continuation), and push that partial continuation onto the chain of continuations hanging off the continuation register.

48 The continuation chain

49 The continuation chain
The continuation register may be a register in the CPU, or it may just be a particular memory location that the implementation uses for this purpose. When we're executing a procedure, we always know where to find a pointer to the partial continuation that lets us resume its caller (or whichever procedure last did a non-tail call). We will sometimes abbreviate this register's name as CONT.

50 The continuation chain
A typical implementation of Scheme has several important registers that encode the state of the currently-executing procedure: The environment register (ENVT) holds the pointer to the chain of environment frames that make up the environment that the procedure is executing in. The program counter register (PC) holds the pointer to the next instruction to execute. The continuation register (CONT) holds the pointer to the chain of partial continuations that lets us resume callers. (This is very roughly the equivalent of an activation stack pointer)

51 The continuation chain
Before we call a procedure, we must save a continuation if we want to resume the current procedure after the callee returns. Since the important state of the currently-executing procedure is in the registers, we will create a record that has fields to hold them, and push that on the continuation chain. We will save the value of the CONT, ENVT, and PC registers in the partial continuation, then put a pointer to this new partial continuation in the continuation registers.

52 The continuation chain

53 The continuation chain
Once a procedure has pushed a continuation, it has saved its state and can call another procedure. The other procedure can use the ENVT, CONT, and PC registers without destroying the values of those registers needed by the caller. To do a procedure return, it is only necessary to copy the register values out of the continuation that's pointed to by the CONT register. This will restore the caller's environment and its pointer to its caller's continuation, and setting the PC register will branch to the code in the caller where execution should resume.

54 The continuation chain
(define (a) (b) #t) (define (b) (c) (define (c) #f)

55 The continuation chain
(define (a) (b) #t) (define (b) (c) (define (c) #f)

56 The continuation chain
(define (a) (b) #t) (define (b) (c) (define (c) #f)

57 The continuation chain
(define (a) (b) #t) (define (b) (c) (define (c) #f)

58 The continuation chain
Most of the time, the rest of this graph becomes garbage quickly - each continuation holds pointers back up the tree, but nothing holds pointers down the tree. Partial continuations therefore usually become garbage the first time they're returned through. The fact that this graph is created on the heap will allow us to implement call-with-current-continuation. call/cc can capture the control state of a program at a particular point in its execution by pushing a partial continuation and saving a pointer to it. Later, it can return control to that point by restoring that continuation, instead of the one in the continuation register.

59 The continuation chain
If you take a pointer to the current continuation, and put it in a live variable or data structure, then that continuation chain will remain live and not be garbage collected. That is, you can "capture" the current state of the stack. If you keep a captured state of the stack around, and later install the pointer to it in the system's continuation register, then you can return through that continuation chain, just as though it were a normal continuation. That is, rather than returning to your caller in the normal way, you can take some old saved continuation and return into that instead

60 The continuation chain
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. We'll call that procedure p the abortable procedure. The current continuation will be 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. We'll call that procedure k an escape procedure. The abortable procedure's argument is the escape procedure that encapsulates the captured continuation.

61 The continuation chain
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

62 The continuation chain
0: (define some-flag #f) 1: (define (my-abortable-proc escape-proc) 2: (display "in my-abortable-proc") 3: (if some-flag 4: (escape-proc "ABORTED")) 5: (display "still in my-abortable-proc") 6: "NOT ABORTED") 7: (define (my-resumable-proc) 8: (do-something) 9: (display (call-with-current-continuation my-abortable-proc)) 10: (do-some-more)) 11: (my-resumable-procedure) At line 11, we call my-resumable-procedure. It calls do-something, and then calls display. But before it calls display it has to evaluate its argument, which is the call to call-with-current-continuation. call-with-current-continuation saves the continuation at that point, and packages it up as an escape procedure. (Line 9) The escape procedure, if called, will return its argument as the value of the call-with-current-continuation form. That is, if the escape procedure is called, it will resume execution of the display procedure, which prints that value, and then execution will continue, calling do-some-more. Once call-with-current-continuation has created the escape procedure, it calls its argument, my-abortable-proc, with the escape procedure as its argument. my-abortable-proc then displays (prints) "in my-abortable-proc." Then it checks some-flag, which is false, and does not execute the consequent of the if---that is, it doesn't execute the escape procedure. It continues executing, displaying "still inmy-abortable-proc." It then evaluates its last body form, the string "NOT ABORTED", which evaluates to itself, and returns that as the normal return value of the procedure call. At this point, the value returned from my-abortable-proc is printed by the call to display in line 9. But suppose we set some-flag to #t, instead of #f. Then when control reaches line 3, the if does evaluate its consequent. This calls the escape procedure, handing it the string "ABORTED" as its argument. The escape procedure resumes the captured continuation, returning control to the caller of call-with-current-continuation, without executing lines 5 and 6. The escape procedure returns its argument, the string "ABORTED" as the value of the call-with-current-continuation form. It restores the execution of my-resumable-proc at line 9, handing display the string "ABORTED" (as the value of its argument form). At this point "ABORTED" is displayed, and execution continues at line 10.

63 6. Association lists An association list is a proper list whose elements are key-value pairs of the form (key . value). Associations are useful for storing information (values) associated with certain objects (keys). procedure: (assq obj alist) procedure: (assv obj alist) procedure: (assoc obj alist) returns: first element of alist whose car is equivalent to obj, or #f These procedures traverse the association list, testing each key for equivalence with obj. f an equivalent key is found, the key-value pair is returned. Otherwise, #f is returned.

64 Association lists The equivalence test for assq is eq?, for assv is eqv?, and for assoc is equal? assq may be defined as follows. (define assq   (lambda (x ls)     (cond       ((null? ls) #f)       ((eq? (caar ls) x) (car ls))       (else (assq x (cdr ls)))))) assv and assoc may be defined similarly, with eqv? and equal? in place of eq?.

65 7. Vectors Vectors are more convenient and efficient than lists for some applications. Vector elements are accessed in constant time. The length of a vector in Scheme is the number of elements it contains. Vectors are indexed by exact nonnegative integers, and the index of the first element of any vector is 0. The highest valid index for a given vector is one less than its length. The elements of a vector may be of any type; a single vector may even hold more than one type of object.

66 Vectors A vector is written as a sequence of objects separated by whitespace, preceded by the prefix #( and followed by ). For example, a vector consisting of the elements a, b, and c would be written #(a b c). procedure: (vector obj ...) returns: a vector of the objects obj ... (vector)    => #() (vector 'a 'b 'c)    => #(a b c)

67 Vectors procedure: (make-vector n) procedure: (make-vector n obj) returns: a vector of length n n must be an exact nonnegative integer. If obj is supplied, each element of the vector is filled with obj; otherwise, the elements are unspecified. (make-vector 0)    => #() (make-vector 0 'a)    => #() (make-vector 5 'a)    => #(a a a a a)

68 Vectors procedure: (vector-length vector) returns: the number of elements in vector The length of a vector is always an exact nonnegative integer. (vector-length '#())    => 0 (vector-length '#(a b c))    => 3 (vector-length (vector 1 2 3 4))  =>  4 (vector-length (make-vector 300))    => 300 procedure: (vector-ref vector n) returns: the nth element (zero-based) of vector n must be an exact nonnegative integer strictly less than the length of vector. (vector-ref '#(a b c) 0)    => a (vector-ref '#(a b c) 1)    => b (vector-ref '#(x y z w) 3)    => w

69 Vectors procedure: (vector-set! vector n obj) returns: unspecified
n must be an exact nonnegative integer strictly less than the length of vector. vector-set! changes the nth element of vector to obj. (let ((v (vector 'a 'b 'c 'd 'e)))   (vector-set! v 2 'x)   v)    => #(a b x d e)

70 Vectors procedure: (vector-fill! vector obj) returns: unspecified
vector-fill! replaces each element of vector with obj. vector-fill! is in the Revised4 Report but not the ANSI/IEEE standard. It may be defined as follows: (define vector-fill!   (lambda (v x)     (let ((n (vector-length v)))       (do ((i 0 (+ i 1)))           ((= i n))           (vector-set! v i x))))) (let ((v (vector 1 2 3)))   (vector-fill! v 0)   v)  =>  #(0 0 0)

71 Vectors procedure: (vector->list vector) returns: a list of the elements of vector vector->list provides a convenient method for applying list-processing operations to vectors. vector->list is in the Revised4 Report but not the ANSI/IEEE standard. (define vector->list   (lambda (s)     (do ((i (- (vector-length s) 1) (- i 1))          (ls '() (cons (vector-ref s i) ls)))         ((< i 0) ls)))) (vector->list (vector))    => () (vector->list '#(a b c))    => (a b c) (let ((v '#(1 2 3 4 5)))   (apply * (vector->list v)))    => 120

72 Vectors procedure: (list->vector list) returns: a vector of the elements of list list->vector is in the Revised4 Report but not the ANSI/IEEE standard. (define list->vector   (lambda (ls)     (let ((s (make-vector (length ls))))       (do ((ls ls (cdr ls)) (i 0 (+ i 1)))           ((null? ls) s)           (vector-set! s i (car ls)))))) (list->vector '())    => #() (list->vector '(a b c))    => #(a b c) (let ((v '#(1 2 3 4 5)))   (let ((ls (vector->list v)))     (list->vector (map * ls ls))))    => #(1 4 9 16 25)


Download ppt "Functional Programming"

Similar presentations


Ads by Google