VIEWS: 3 PAGES: 12 CATEGORY: Computers & Internet POSTED ON: 2/1/2010 Public Domain
• PracticalSessions » DataAbstraction Practical Session #4 – Simple Program Verification; Data Abstraction Part 1 – Simple Program Verification Relevant book section: Paulson 6.3 We will show that the requirements for (several parts of) the contract are met. We will deal with: 1) Type correctness 2) Post conditions hold if pre-conditions hold Type Correctness Both parameters and the returned value should adhere to the types specified within the contract. This is done with Type Inference. The basic method is: 1) For parameters - to examine how they are used inside the procedure. If they are passed to a procedure whose argument types are known (such as the primitive +, or a procedure for which we already verified the types), we may assume they are of the correct type. 2) For the returned value – basically the same method. If we return an atomic expression, its type is the returned type. If we apply another procedure (and return the value of its application as the result), the returned type is that of the procedure. N.B: Complete correction proofs for complicated functions are beyond the scope of this course. Example 1 - recursive Fibonacci series calculation: ;;; 1. Signature: (fib n) ;;; 2. Type: Number -> Number ;;; 3. Purpose: to compute the nth Fibonacci number. ;;; 4. Example: (fib 5) should produce 5 ;;; 5. Pre-conditions: n >= 0, ;;; 6. Post-conditions: result = nth Fibonacci number. 1 ;;; 7. Tests: (fib 3) expected value 2 ;;; (fib 1) expected value 1 (define (fib n) (cond ((= n 0) 0) ((= n 1) 1) (else (+ (fib (- n 1)) (fib (- n 2)))))) Proof of type correctness: 1. The parameter n is an argument to the procedures '=' and ‘-‘, and is therefore a Number. 2. We apply the function '+' in order to get the returned value, so our returned value must be a Number. Remark: In Scheme the type of a variable is not explicitly defined when the variable is defined, it can only be inferred. Example 2 – The Function move-left-up ;;; 1. Signature: move-left-up(f n) ;;; 2. Type: [Number -> Number]*Number -> [Number -> Number] ;;; 3. Purpose: to create a function which is similar to f, but its graph is moved by n units to the left and up ;;; 4. Example: (move-left-up square 3) should return g(x) s.t. g(x)=((x+3)^2)+3. ;;; ((move-left-up square 3) 4) should return (4+3)^2+3 = 52 ;;; 5. Pre-conditions: f is defined for all x. n is a natural number (this is relied upon in the post –condition induction based proof). ;;; 6. Post-conditions: result=g(x)=n+f(x+n). g's values are similar to f's but are shifted left and up by n units (same for the graphs). ;;; 7. Tests: 1. ((move-left-up square 3) 4) value: 52, as expected ;;;; 2. ((move-left-up square 4) 2) value: 40, as expected ;;;; 8. definition 2 (define (move-left-up f n) (lambda (x) (+ n (f (+ x n)))) ) Proof of type correctness: 1. The parameter f is applied to the result of a summation, and yields an argument for ‘+’, so is a function Number->Number. 2. n is an argument to ‘+’ so it is a number. 3. The returned value is the value of the 'lambda' special operator, which is a closure. The parameter to this closure is x which is an argument to ‘+’, so it is a number. The returned value is the result of a summation, and is therefore a number. Post conditions Here we prove by induction (assuming the pre-conditions are met): 1) Base – when the function is called with values resulting in no recursive calls. 2) Hypothesis – assume the post-conditions hold for every integer in [0,n]. (n is natural) 3) Step – prove that the requirements are still met for n+1. 1. Typically, the only non-trivial step is #3. Expand the call f(…, n+1, …) by definition, and try to achieve an expression containing the call to f(..., n, …), which may be replaced under the Hypothesis. Example – fast exponentiation ;;; 1. Signature: fast-exp(b, n) ;;; 2. Type: Number -> Number ;;; 3. Purpose: To compute the exponent n of a natural number b in O(log2(n)) steps, according to the following rules: ;;; 1. n=0 expt = 1; 2. n is even expt(b,n) = square(expt(b, n/2)) 3. n is oddexpt(b,n) = n*expt(b, n-1) ;;; 4. Example: fast-exp(2,5) should return 32 ;;; 5. Pre-conditions: n is a non-negative integer. If n is zero, b must not be zero. 3 ;;; 6. Post-conditions: result=b^n. ;;; 7. Tests: (define (fast-exp b n) (cond ((= n 0) 1) ((even? n) (square (fast-exp b (/ n 2)))) (else (* b (fast-exp b (- n 1)))))) Inductive proof of post-conditions: (we prove by complete induction, which is a variant of the basic mathematical induction—when correctness for n+1 stems not only from correctness for n, but from the correctness for 1..n ) 1) Base: n=0 result = 1 = b^0, for every b<>0. Also, for n=1 result = b *fast-exp(b, 0) = b*1 = b = b^n 2) Hypothesis: assume for values in [0,n] the condition holds 3) Inductive step: a. n is even n+1 is odd. Fast-expt(b, n+1) = b*fast-expt(b, n) = b*b^n = b^(n+1) b. n is odd n+1 is even. Fast-expt(b, n+1) = square(fast- expt(b,(n+1)/2)). We may assume n>1 (we already proved the claim for n<=1), and thus (n+1)/2<n square(fast-expt(b,(n+1)/2)) = square(b^[(n+1)/2]) = b^(n+1) as required. Part 2 – Data Abstraction Relevant book section SICP Section 2.1 Concepts Procedure abstraction: o Separate the way the procedure would be used from the details of how the procedure is implemented in terms of hopefully simpler procedures. Data abstraction: o A methodology that enables us to isolate how a compound data object is used from the details of how it is constructed from more primitive data objects. 4 Data abstraction: Client programs use "abstract data". Make no unnecessary (internal) assumptions about the data that are not strictly necessary for performing the task at hand. At the same time, a "concrete" data representation (or implementation) is defined independent of the programs that use the data. o There are many ways to implement a single concrete data type o It shouldn't change the code of the "abstract" (client) program. The interface between concrete and abstract data representations is a set of procedures, called selectors and constructors that implement the abstract data in terms of the concrete representation. Correctness rules determines the correctness of various implementations (that still can vary by other features, like efficiency, stability, etc.). The operations of an ADT are like a “family”: There implementations depend on each other. Example: Geometric Operations for Circles Design a set of procedures for manipulating circles on x-y plane. Suppose we want to do geometric calculations with circles. Desired (high-level) actions: Move a circle Scale a circle Get the area of a circle Compare circles Check if circles intersect Add circles (create a different circle) 5 Circle Abstract Data-structure (Type ADT) can be represented by the components: x-center y-center radius Interface (selectors, constructors) [to be implemented later…] (make-circle ) returns the circle. (get-x-center ) returns the x-center of the circle . (get-y-center ) returns the y-center of the circle (get-radius ) returns the radius of the circle Definition of abstract data type Circle: 1. ; move circ center point by x on x-axis and by y on y-axis 2. ; [Circle, Number, Number -> Circle] 3. (define (move-circle circ x y) 4. (make-circle (+ (get-x-center circ) x) 5. (+ (get-y-center circ) y) 6. (get-radius circ))) 7. 8. ; scale circ by scale 9. ; [Circle, Number -> Circle] 10. (define (scale-circle circ scale) 11. (make-circle (get-x-center circ) 12. (get-y-center circ) 13. (* (get-radius circ) scale))) 14. 15. ; calculate the area of circ 16. ; [Circle -> Number] 17. (define (area-circle circ) 18. (let ((pi 3.14)) 19. (* pi (square (get-radius circ))))) 6 20. 21. ; compare two circles 22. ; [Circle, Circle -> Boolean] 23. (define (equal-circles? circ1 circ2) 24. (and (=(get-x-center circ1) (get-x-center circ2)) 25. (=(get-y-center circ1) (get-y-center circ2)) 26. (=(get-radius circ1) (get-radius circ2)))) 27. ; auxiliary procedure – distance 28. ; [Circle, Circle -> Number] 29. (define (distance-circles circ1 circ2) 30. (let ((x-diff (- (get-x-center circ1) (get-x-center circ2))) 31. (y-diff (- (get-y-center circ1) (get-y-center circ2))) 32. (let 33. ((center-dist (sqrt (+ (square x-diff) (square y-diff))))) 34. center-dist))) 35. 36. ; check if circles intersect 37. ; they intersect if the distance between the center of the two 38. ; circles is smaller that the sum of the radiuses of the circles. 39. ; [Circle, Circle -> Boolean] 40. (define (intersect-circles? circ1 circ2) 41. (let ((center-dist (distance-circles circ1 circ2)) 42. (sum-radiuses (+ (get-radius circ1) (get-radius circ2)))) 43. (let 44. ((answer (> sum-radiuses center-dist))) 45. answer))) 46. 47. ; add circles. Return a circle with an average center and some of radiuses of both circles 48. ; [Circle, Circle -> Circle] 49. (define (add-circles circ1 circ2) 50. (let ((new-x-center (average (get-x-center circ1) 51. (get-x-center circ2))) 52. (new-y-center (average (get-y-center circ1) 53. (get-y-center circ2))) 54. (new-radius (+ (get-radius circ1) 55. (get-radius circ2)))) 56. (make-circle new-x-center new-y-center new-radius))) 7 The operations (move-circle, etc) are defined in terms of data objects (radius, x, y, circle) Circle "Concrete data" representation How can we tell that the "concrete data" representation is a good one? The constructor and selectors must satisfy specified conditions that make the representation valid. for every real numbers x-center, y-center and radius: 1. (get-x-center (make-circle x-center y-center radius)) = x-center 2. (get-y-center (make-circle x-center y-center radius)) = y-center 3. (get-radius (make-circle x-center y-center radius)) = radius Correctness rules: Describe everything that is expected from the behavior of any “concrete data” implementing the ADT’s interface. Describe the relationship and interactions between the results of combinations of consecutive calls to the different procedures in the ADT’s interface. Implementation #1 (procedural "eager" style) constructor: General remark: Some contracts should be added to the procedures in this ps. ; [Number, Number, Number -> [{0, 1, 2} -> Number] ] 1. (define (make-circle x-center y-center radius) 2. (let ((dispatch (lambda (m) 3. (cond ((= m 0) x-center) 4. ((= m 1) y-center) 5. ((= m 2) radius) 6. (else (error "Illegal argument – make-circ " m)))))) 7. dispatch)) selectors: 1. ; [ [{0, 1, 2} -> Number] -> Number] 2. (define (get-x-center circ) (circ 0)) 8 3. ; [ [{0, 1, 2} -> Number] -> Number] 4. (define (get-y-center circ) (circ 1)) 5. ; [ [{0, 1, 2} -> Number] -> Number] 6. (define (get-radius circ) (circ 2)) Example: evaluation steps in the substation model (applicative order) for (get-radius (make-circle 10 11 12)) (This is a sketch of running the EVAL algorithm:) 1. 1. (get-radius (make-circle 10 11 12)) 2. 2. (get-radius 3. (lambda (m) 4. (cond ((= m 0) 10) 5. ((= m 1) 11) 6. ((= m 2) 12) 7. (else 8. (error " Illegal argument - make-circ " m)))) ) 9. 3. ((lambda (m) 10. (cond ((= m 0) 10) 11. ((= m 1) 11) 12. ((= m 2) 12) 13. (else 14. (error " Illegal argument - make-circ " m)))) 15. 2) 16. 4. (cond ((= 2 0) 10) 17. ((= 2 1) 11) 18. ((= 2 2) 12) 19. (else 20. (error " Illegal argument - make-circ " 2))) 21. 5. 12 9 Notes: The value returned by make-circ is a procedure – dispatch Validity of correctness rules: The selector, for example (get- y-center circ), applies circ to 1. If circ is the procedure formed by (make-circ x y z), then circ applied to 1 will yield y. High-order procedures provide the ability to represent compound data. Implementation #2 (procedural "lazy" style) This is a different procedural representation of circles. selectors: 1. ;;; Type: [ // get-x-center 2. ; [ // circ 3. ; [ Number, Number, Number -> Number] 4. ; -> Number] 5. ; -> Number ] 6. (define (get-x-center circ) 7. (circ (lambda (a0 a1 a2) a0))) 8. ; in the lambda: return the first parameter 9. 10. ; [ [ [ Number, Number, Number -> Number] -> Number] -> Number ] 11. (define (get-y-center circ) 12. (circ (lambda (a0 a1 a2) a1))) 13. 14. ; [ [ [ Number, Number, Number -> Number] -> Number] -> Number ] 15. (define (get-radius circ) 16. (circ (lambda (a0 a1 a2) a2))) 10 Constructor: 1. ; [Number, Number, Number 2. ; -> [ 3. ; [ Number, Number, Number -> Number] 4. ; -> Number] 5. ;] 6. (define (make-circle x-center y-center radius) 7. (lambda (m) (m x-center y-center radius))) Difference between lazy style and eager style procedure implementation: In lazy style, most the logic/complexity is in the selectors, whereas in eager style implementation, it is in the constructor. Efficiency of computation: The eager style prepares everything in the construction of data values. Selection is immediate. The lazy style – does nothing in construction time. Selection is more expensive. Question: Would you that the ADT “family” in eager style is closed ( already defined data values cannot have new selectors), while in lazy style, the family is open for new members? Answer: both implementations require some changes when adding new members, although the lazy implementation is more "open", since it may be possible to add new selectors without altering the constructor (as long as the "data" doesn’t change) Check Validity of this representation: Example: (get-y-center (make-circ 10 11 12))=11 evaluation steps: 1. (get-y-center (make-circle 10 11 12)) 2. (get-y-center (lambda (m) (m 10 11 12))) 3. ( (lambda (m) (m 10 11 12)) (lambda (a0 a1 a2) a1)) ) 4. ( (lambda (a0 a1 a2) a1)) 10 11 12 ) 5. 11 11 Summary – Abstraction levels: (draw the abstraction diagram) Client Programs that use circles… Abstract data move-circle, equal-circles?… Interface impl., concrete: make-circle, get-x-center, get-radius … Implementation level uses procedures(eager/lazy…) 12