# Principles of Programming Languages - 2007Spring by yrs83496

VIEWS: 3 PAGES: 12

• pg 1
```									• 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 oddexpt(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

   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

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

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)
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)
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))
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))
43.      (let
46.
47. ; add circles. Return a circle with an average center and some of radiuses of both circles
48. ; [Circle, Circle -> Circle]
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)))

7
The operations (move-circle, etc) are defined in terms of data objects

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

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

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)

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

(This is a sketch of running the EVAL algorithm:)

1.    1.   (get-radius (make-circle 10 11 12))

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 ]

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

```
To top