עקרונות שפות תוכנות 1122 'סמסטר ב :5 תרגול Compound Data and Data Abstraction Topics 1. Type Pair 2. Type List 3. Data Abstraction (ADTs, Pair-based and List-Based implementations). Type Pair Pair is a Type implemented in Scheme, representing an ordered pair of values (of any type). A pair(<a1>.<a2>) is constructed by applying cons over <a1> and <a2>. a pair's type is PAIR(T1,T2). The Pair type is implemented as part of Scheme (primitive). Pair –Operations: cons: Create a Pair. car: Get the Pair’s first element cdr: Get the Pair’s second element Type:T1 * T2 -->PAIR(T1, T2) Type: PAIR(T1, T2) --> T1 Type: PAIR(T1, T2) --> T2 pair?:check if an expression is a Pair Type: T --> Boolean equal?: check the equality of two Pairs Type: T1 * T2 --> Boolean Examples (The box-pointer drawing is for visualization only) > (cons 1 2) (1 . 2) This is the printing form of pairs. 1 2 > (cons 1 (cons 2 (lambda () (+ 1 2)))) (1 2 . #<procedure>) 1 2 1 #<procedure> > (cons (car (cons 1 2)) (cdr (cons 3 4))) (1 . 4) >(define c (cons 1 (cons 'cat (cons 3 (cons 4 5))))) >c (1 cat 3 4 . 5) > (cdddr c) (4 . 5) > (cons (cons 1 2) (cons 1 3)) ((1 . 2) 1 . 3) 1 2 1 3 Type LIST List is a Type implemented in Scheme, representing an ordered collection of values (of any type). A sequence <a1>, <a2>, ...<an> is constructed by repeated applications of the cons starting from the empty list: (cons <a1> (cons <a2> (cons ... (cons <an> (list)) ...))). a list's type is either LIST if it is heterogeneous or LIST(T) for homogeneous lists with elements all of type T. List – Operations: list: Creates a list. (list): Creates null (an empty list). cons: Creates a list when used correctly. Type: T*T*…*T->List(T) Type: List Type: T*List(T)->List(T) car: Gets the list’s first element. cdr: Gets the list without the first element. Type: List(T)->T Type: List(T)->List(T) list?: Checks if an expression is a list. null?: Check if an expression is null (an empty list). equal?: Checks equality of lists. Type: T->Boolean Type: List(T)->Boolean Type: List(T)*List(T)->Boolean Other operations: append, length and many more. Examples >(define L1 (list)) >(null? L1) #t >(length L1) 0 >(define L2 (list 1 (list 2 3) 6)) 2 >L2 (1 (2 3) 6) > (car L2) 1 > (caadr L2) 2 > (cadadr L2) 3 >(cdddr L2) () > (list? (cons 2 (cons (list 2 3) 4))) #f > (list (list 1 2) 1 2) 1 1 2 2 Question – Draw the box-pointer diagram of the list below, representing a Scheme expression: (list ‘cond (list (list ‘= ‘n ‘0) ‘0) (list (list ‘= ‘n ‘1) ‘1) (list ‘else (list ‘+ (list ‘fib (list ‘- ‘n ‘1)) (list ‘fib (list ‘- ‘n ‘2))))) Example – filter Signature: filter(predicate, sequence) Purpose: return a list of all sequence elements that satisfy the predicate Type: [[T-> Boolean]*LIST(T) -> LIST(T)] (define filter (lambda (predicate sequence) (cond ((null? sequence) (list)) ((predicate (car sequence)) (cons (car sequence) (filter predicate (cdr sequence)))) (else (filter predicate (cdr sequence)))))) >(filter pair? (list (cons 'a 'b)'c (cons 'd (cons 'e 'f)) 'g)) ( (a. b) (d . (e . f)) ) 3 Data Abstraction Data abstraction is a methodology that separates compound data usage (the Client/Concept Level) from compound data implementation(Supplier/Concrete Level), using an Abstract Data Type barrier. An ADT is a specification that includes: o o An interface (A set of Contracts for a value constructor, various operators, an identifier), and Invariants that describe the behavior/inter-dependence of the ADTs procedures. Client programs use ADTs without knowledge about the implementation. The decision which implementation is used should not change the code of the Client. An implementation is developed independently of the client. An implementation is a fulfillment of the ADT’s contract, which respects the ADTs invariants. There may be several implementations for a single ADT, varying in features, like efficiency, inner-representation, etc. Example 1 - The Circle ADT: Goal: Design an abstract data type for manipulating circles on a plane. Circle ADT Definition Desired (Client) actions: Move a circle, Get the area of a circle. 1. Interface (Data operations' specification): ;;; 1. Signature: make-circle(x-center y-center radius) ;;; 2. Purpose: constructs a circle. ;;; 3. Type: Client view: [Number* Number* Number -> Circle ] ;;; 4. Pre-conditions: radius >= 0. ;;; 1. Signature: get-x-center(circle) ;;; 2. Purpose: returns the x coordinate of the circle's center. ;;; 3. Type: Client view:[ Circle -> Number] ;;; 1. Signature: get-y-center(circle) ;;; 2. Purpose: returns the y coordinate of the circle's center. ;;; 3. Type: Client view:[ Circle -> Number] ;;; 1. Signature: get-radius(circle) ;;; 2. Purpose: returns the radius of the circle. ;;; 3. Type: Client view: [Circle -> Number] ;;; 1. Signature: circle? (x) ;;; 2. Purpose: returns true iff x is a circle. ;;; 3. Type: [T -> Boolean] 4 ;;; 1. Signature: circ-equal?(circ1 circ2) ;;;2. Purpose: Compare two circles. ;;;3.Type: Client view: [Circle * Circle -> Boolean] NOTE: Circle is an abstract type: It is not supported by the language Scheme or by the Type specification grammar. It is user-defined and is known only to the programmers. Therefore, the client programs can refer to ADT Circle, but the implementation of Circle must use the types that Scheme supports (or other ADTs). 2. Invariants: 1. For all x, y, r it holds: (get-x-center (make-circle x yr)) = x (get-y-center (make-circle x yr)) = y (get-radius (make-circle x yr)) = r 2. For every e (circle? e) = #t iff there exist x,y,r s.t. e is the value of (make-circle x y r). 3. For every e1 and e2 (circ-equal? e1 e2) = #t iff there exist x,y,r s.t. both e1 and e2 are the value of (make-circle x y r). Usage –Client Side The client knows only the above specification and so it is all the client needs in order to work with circles. ;;; 1. Signature: move-circle(circ x y ) ;;; 2. Purpose: return a circle, with a center point that is transposed by (x,y). ;;; 3. Type: Client view: [Circle*Number*Number -> Circle] (define move-circle (lambda (circ x y) (make-circle (+ (get-x-center circ) x) (+ (get-y-center circ) y) (get-radius circ)))) ;;; 1. Signature: area-circle(circ) ;;; 2. Purpose: Calculate the area of circ ;;; 3. Type: Client view: [Circle -> Number] (define area-circle (lambda (circ) (let((pi 3.1459)) (* pi (square (get-radius circ)))))) 5 An Implementation of Circle ADT Using Pairs: We will represent our ADT as a pair of pairs: 'circle radius x-center y-center The label 'circle' in front of every circle implementation is our only way to distinguish circles from other pairs. It serves like a type-tag in syntactically typed languages. ;;;Type: Supplier view: [Number* Number* Number Pair(Symbol,Pair(Number,Pair(Number,Number))) ] (define make-circle (lambda (x-center y-center radius) (cons 'circle (cons radius (cons x-center y-center))))) NOTE: Here it is important to specify the concrete type, recognized by Scheme, while the abstract type is not a Scheme type. So, in this implementation Circle =Pair(Symbol, Pair(Number, Pair(Number, Number))) The following three selectors have the same type, ;;;Type: Supplier view: [ Pair(Symbol,Pair(Number,Pair(Number,Number))) -> Number] ;;; Pre-condition: (circle? circ)-->#t (define get-x-center (lambda (circ) (car (cdr (cdr circ)))))(Or caddr) (define get-y-center (lambda (circ) (cdddr circ))) (define get-radius (lambda (circ) (cadr circ))) ;;;Type: Supplier view: [ T -> Boolean] (define circle? (lambda (circ) (and (pair? circ) (eq? (car circ) 'circle)))) ;;;Type: Supplier view: [Pair(Symbol, Pair(Number, Pair(Number, Number))) * Pair(Symbol, Pair(Number, Pair(Number, Number))) Boolean] ;;; Pre-condition: (circle? circ1) and (circ? circ2)-->#t (define circ-equal? (lambda (circ1 circ2) (and (=(get-x-center circ1) (get-x-center circ2)) (=(get-y-center circ1)(get-y-center circ2)) 6 (= (get-radius circ1) (get-radius circ2))))) Proving the invariants of an ADT The set of procedures must fulfill the interface and respect the invariants of the ADT, for it to be considered an implementation of the ADT. We prove the 3rd rule for the get-radius selector: We show that for every x, y and r it holds that applicative-eval [(get-radius (make-circle xyr))] = r. (This is a sketch of running the applicative-eval algorithm:) applicative-eval[ (get-radius (make-circle x y r)) ] =>* applicative-eval[ (get-radius ('circle . (r .(x . y )))) ] ==>* applicative-eval[ (car (cdr ('circle . (r .(x . y ))))) ] ==>* applicative-eval[ (car (r .( x . y ))) ] ==>* r. Example 2 - Symbolic Differentiation: This problem was, historically, one of the motivating problems for the development of computer languages for symbolic manipulation. It led to the current powerful systems for symbolic mathematical manipulations. Derivation without symbolic manipulations (if time permits) First, we will show how to compute the derivative of a function at a specific point. Here is a function which receives a function f and a size delta dx, and returns a function which when given point x, returns the derivative of function f in that point. Signature: deriv(f dx) Purpose: to construct a procedure that computes the derivative dx approximation of a function: deriv(f dx)(x) = (f(x+dx) - f(x) )/ dx Type: [Number -> Number] * Number -> [Number -> Number] Pre-conditions: dx < 1 Post-condition: result=closure r, s.t. (r x) = (/ (- (f (+ x dx)) (f x)) dx) (define deriv (lambda (f dx) (lambda (x) (/ (- (f (+ x dx)) (f x)) dx)))) PROBLEM: The above derivation function gives us the value of derivative of a function only at a certain point. It's more useful and accurate to find an actual expression for the derivative, if possibly. For a polynomial function this is easy to do the use of symbols. 7 Goal: Write a procedure for differentiation of expressions, built with additions and multiplications, with at most two arguments. For example: 2 The derivation of ax + bx + c , by x, where a, b, c are numbers is 2ax + b. Note that the expressions are handled here as DATA, not as PROCEDURES. Example: 𝑑 (𝑥(𝑦 + 5)) 𝑑𝑥 > (derive (make-product (make-variable 'x) (make-sum (make-variable 'y) (make-constant 5)) (make-variable 'x))) (+ y 5) The derivation rules are: o o o o dc 0 , if c is a constant or a variable different from x. dx dx 1. dx d(u v) du dv . dx dx dx d(u v) du dv v u dx dx dx Looking into the derivation task we see that we need the following types of algebraic expressions: **** variable, constant, sum, product **** These types will have, each, constructors and selectors. For example, the SUM type needs a constructor for "making" a sum, and selecting its elements. The Algebraic Expression (AExpression) ADT: We present here the interface specification, without the invariants. CONSTANT: (make-constant <e>) (constant? <e>) VARIABLE: (make-variable <e>) (variable? <e>) (same-variable? <v1><v2>) SUM: (make-sum <e1><e2>) (sum? <e> ) 8 (augend<e>) (addend<e>) PRODUCT: (make-product <e1><e2>) (product? <e>) (multiplier<e>) (multiplicand<e>) Usage – Client Side ;;; Signature: derive(exp var) ;;; Purpose: find the derivative of an algebraic expression according to a variable ;;; Type: [AExpression * AExpression ->AExpression] ;;; Pre-condition (variable? var) (define derive (lambda (exp var) (cond ((constant? exp) (make-constant 0)) ((variable? exp) (if (same-variable? exp var) (make-constant 1) (make-constant 0))) ((sum? exp) (make-sum (derive (augend exp) var) (derive (addend exp) var))) ((product? exp) (make-sum (make-product (multiplier exp) (derive (multiplicand exp) var)) (make-product (derive (multiplier exp) var) (multiplicand exp)))) (else (error "unknown expression type -- DERIVE" exp))))) Implementing the algebraic expression ADT using lists We choose the implement algebraic expressions as lists, written in prefix notation. For example, ax+b would be represented as the list: (+ (* a x) b). The symbols '+ , '* serve as type tags. The implementation defines AExpression = Number union Symbol union List. Lists are used for composite data (sum, product) where the operation symbols '+ and '* tag the data. ;;; Type: Number -> Number (define make-constant (lambda (x) x )) (define constant? (lambda(x) (number? x))) 9 ;;; Type: Symbol -> Symbol (define make-variable (lambda(x) x)) ;;; Type: T-> Boolean (define variable? (lambda(x) (symbol? x))) ;;; Type: Symbol* Symbol-> Boolean ;;; Pre-conditions: (variable? v1) and (variable? v2) (define same-variable? (lambda(v1 v2) (eq? v1 v2))) ;;; Type: (Number union Symbol union List)*(Number union Symbol union List) -> List (define make-sum (lambda(a1 a2) (cond ((and (constant? a1) (constant? a2)) (make-constant (+ a1 a2))) ((and (constant? a1) (= a1 0)) a2) ((and (constant? a2) (= a2 0)) a1) (else (list '+ a1 a2))))) ;;; Type: T -> List (define sum? (lambda(x) (and (list? x) (equal? (car x) '+)))) ;;; Type: List -> T (define augend (lambda(s) (cadr s))) (define addend (lambda(s) (caddr s))) ;;; Type: T*T -> List (define make-product (lambda(m1 m2) (cond ((and (constant? m1) (constant? m2)) (make-constant (* m1 m2))) ((and (constant? m1) (= m1 0)) (make-constant 0)) ((and (constant? m1) (= m1 1)) m2) ((and (constant? m2) (= m2 0)) (make-constant 0)) ((and (constant? m2) (= m2 1)) m1) (else (list '* m1 m2))))) ;;; Type: T -> Boolean (define product? (lambda(x) (and (list? x)(eq? (car x) '*)))) ;;; Type: List -> T (define multiplier (lambda(p) (cadr p))) ;;; Type: List -> T (define multiplicand (lambda(p) (caddr p))) 10 > (derive (make-sum (make-variable 'x) (make-constant 6)) (make-variable 'x)) 1 𝑑 (𝑥(𝑦 + 5)) 𝑑𝑦 > (derive (make-product (make-variable 'x) (make-sum (make-variable 'y) (make-constant 5))) (make-variable 'y)) x 11