עקרונות שפות תוכנות 1122 'סמסטר ב Data Abstraction continued –6 תרגול Topics 1. Procedural Abstraction (Eager/Lazy Procedural implementations) 2. The Sequence Interface Procedural Abstraction Circle ADT Implementation Example – Eager procedural implementation: High-order procedures provide the ability to represent compound data and retrieve it using message passing. We can represent an object by a procedure of type Symbol->T. The procedure gets a message as a symbol and returns the wanted data. ;;;Type: Supplier view: [Number* Number* Number Pair (Symbol, [Symbol --> Number] )] (define make-circle (lambda (x-center y-center radius) (cons 'circle (lambda (m) (cond ((eq? m 'x) x-center) ((eq? m 'y) y-center) (else radius)))))) Note: This implementation builds a pair data structure, where the 2nd element of the pair is a procedure. So, in this implementation Circle=Pair (Symbol, [Symbol --> Number] )]. ;;; Type: Supplier view: [ Pair(Symbol, [Symbol -> Number] ) -> Number] ;;; Pre-condition: (circle? circ)-->#t (define get-x-center (lambda(circ) ((cdr circ) 'x))) (define get-y-center (lambda(circ) ((cdr circ) 'y))) (define get-radius (lambda(circ) ((cdr circ) 'r))) Note: circle? and equal-circ? are the same as in the previous implementation. 1 Invariants: We prove the 3rdinvariant for the get-radius selector. For every x, y and r it holds that applicative-eval[ (get-radius (make-circle x y r))]==> 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 .<closure(m) (cond ((eq? m 'x) x) ((eq? m 'y) y) (else r)))) ] ==> applicative-eval[(<closure (m) (cond ((eq? m 'x) x) ((eq? m 'y) y) (else r)))> 'r)]==> applicative-eval[(cond ((= 'r 'x) x) ((= 'r 'y) y) (else r))] ==>r. Circle ADT Implementation Example– Lazy procedural implementation: This is a procedural representation in which the constructor encapsulates the data of the constructed object within a procedure that expects a selector as a parameter. ;;; Type: Supplier view: [Number*Number*Number -> Pair(Symbol ,[ [Number*Number*Number->T ]->T ])] (define make-circle (lambda(x-center y-center radius) (cons 'circle (lambda (sel) (sel x-center y-center radius))))) The procedure gets a selector sel and applies it on the circle's data. Here Circle = Pair(Symbol ,[ [Number*Number*Number->T ]->T ]). ;;; Type: Supplier view: [ Pair(Symbol, [Symbol -> Number] ) -> Number] ;;; Pre-condition: (circle? circ)-->#t (define get-x-center (lambda(circ) ((cdr circ) (lambda (x y r) x)))) (define get-y-center (lambda(circ) ((cdr circ) (lambda (x y r) y)))) (define get-radius (lambda(circ) ((cdr circ) (lambda (x y r) r)))) Note circle? and equal-circle? are the same as in eager implementation. 2 Invariants: We prove the invariants of the y coordinate: For each x y and r, it holds that (get-y-center (make-circle x y r))=y: applicative-eval(get-y-center (make-circle x y r)) ==> applicative-eval(get-y-center (cons 'circle <closure (sel) (sel x y r)>)) ==> applicative-eval (<closure (circ)((cdr circ) (lambda (x y r) y))> (cons 'circle <closure (sel) (sel x y r)>)) ==> renaming applicative-eval (<closure (circ1)((cdr circ1) (lambda (x2 y3 r4) y3))> (cons 'circle <closure (sel5) (sel5 x y r)>)) applicative-eval((cdr(cons 'circle <closure (sel5) (sel5 x y r)>)) (lambda (x2 y3 r4) y3)) applicative-eval(<closure(sel5) (sel5 x y r)> <closure (x2 y3 r4) y3)> )==> applicative-eval( <closure (x2 y3 r4) y3)> x y r ) ] ==> y. Difference between lazy and eager procedural implementations In lazy implementation all logic is in the selectors, in the eager implementation it is in the constructor. Extendibility: eager implementation cannot be extended as easily as the lazy one. (Eager: rewrite the constructor and add a simple selector, lazy: add selector). The Sequence Interface Scheme provides powerful support for sequences in the form of operations for sequence manipulation (map, filter and such) since they abstract away the element-by-element manipulation. Using the provided operations, the implementation of a sequence is hidden from the user. Understanding the structure of sequences is not needed for users. Thus, the sequence is an ADT and the sequence manipulation operations are its interface. Aggregates in relation to the sequence interface: An aggregate is a collection of items that are gathered together to form a total quantity. Object-oriented programming languages support a variety of interfaces and implementation utilities for aggregates, like Iterable, Collection, Set, List, Array etc. These interfaces declare standard collection services like has-next(), next(), item-at(ref), size(), is-empty(), add(item)and more. Functional languages provide furthermore, powerful sequence operations that put an abstraction barrier (ADT interface) between clients of sequence applications to the sequence implementation. The advantage is the separation between usage and implementation: The ability to develop abstract level client applications, without any commitment to the exact sequence implementation. Sequence operations 3 abstract away the element-by-element sequence manipulation. Using sequence operations, client procedures become clearer, and their uniformity stands out. Example 1 – Unlabeled Trees (1 ((2 3) 4) (5)) A tree can be represented as a list of lists. Let us consider a tree that has values only in its leaves. 1 For example, (list 1 (list (list 2 3) 4) (list 5)) (5) ((2 3) 4) is a tree. (2 3) 5 4 2 Version 1 – without sequence operations 3 ;;; Signature: duplicate-tree (tree) ;;; Purpose: Return a tree, with same structure as tree, ;;; except each leaf value is replaced with a list of that leaf value. ;;; Type: [T -->LIST] ;;; Post-conditions: ;;; a. The result is a tree with height increased by 1. ;;; b. The result is a tree including the same values. ;;; c. Each value appears in the result twice as much as it appeared in tree. (define duplicate-tree (lambda(tree) (cond ((null? tree) (list)) ((not (list? tree)) (list tree tree)) (else (cons (duplicate-tree (car tree)) (duplicate-tree (cdr tree))))))) >(duplicate-tree 'a) (a a) >(duplicate-tree (list 'a 'b) ) ((a a) (b b)) (a b) (a b) a b (bb) (aa) 4 a a b b >(duplicate-tree (list 1 (list (list 2 3) 4) (list 5)) ) ((1 1) (((2 2) (3 3)) (4 4)) ((5 5))) Different versions of duplicate-tree using sequence operation map. Version 2.a – Using Sequence operations Type: LIST -> LIST (define duplicate-tree (lambda (tree) (map (lambda(sub-tree) (if (list? sub-tree) (duplicate-tree sub-tree) (list sub-tree sub-tree))) tree))) Using sequence operations improve abstraction level. But does not respect contract type: It accepts only lists because of the type of map. Version 2.a – Using Sequence operations Type: T->LIST (define duplicate-tree (lambda (tree) (if (not (list? tree)) (list tree tree) (map duplicate-tree tree)))) Example 2 - Nested mappings Recall the sequence operation from class: Signature: accumulate(op, initial, sequence) Purpose: Accumulate by 'op' all sequence elements, starting (ending) with 'initial' Type: [T1 * T2 --> T2] * T2 * LIST(T1) --> T2 (define accumulate (lambda (op initial sequence) (if (null? sequence) initial 5 (op (car sequence) (accumulate op initial (cdr sequence)))))) The flattening of a list using accumulate with append is popular and can be abstracted: (define flatmap(lambda (proc seq) (accumulate append (list) (map proc seq)))) For example, >(define lst1 ((1 2 3) (4 6 7) (8 9))) >(accumulate append (list) lst1) (1 2 3 4 6 7 8 9) >(flatmap (lambda(x) x) lst1) (1 2 3 4 6 7 8 9) >(flatmap (lambda(lst) (filter (lambda(x) (= (modulo x 2) 1)) lst)) lst1) (1 3 7 9) map accepts a procedure that works on elements of the list. flatmap accepts a list of lists, so the procedure it accepts works on elements that are lists. Goal: Compute all permutations of a set S. Solution approach: 1. If S is empty -- (list). 2. If S is not empty -- compute all permutations of S-x (for some x in S), and adjoin x in front. (define remove (lambda (item sequence) (filter (lambda (x) (not (= x item))) sequence))) (define permutations (lambda (s) (if (null? s) ; empty set? (list (list)) ; sequence containing empty set (flatmap (lambda (x) (map (lambda (p) (cons x p)) (permutations (remove x s)))) s)))) 6 > (permutations (list 2 5 7)) ((2 5 7) (2 7 5) (5 2 7) (5 7 2) (7 2 5) (7 5 2)) It is advised to look at a running example of this Comparison to Java Here is how the same code looks like in Java: // this function returns the string s with the i-th character removed. // assumes that i is a position in the string public static String delete(String s, int i){ return s.substring(0,i)+ s.substring(i+1,s.length()); } // Prints all the permutation of a string. First call the method with parameters perm(s,""). // Iterative solution with accumulator string acc public static void perms(String s, String acc){ if (s.length()==0) System.out.println(acc); else for (int i=0; i<s.length(); i=i+1) perms(delete(s, i), acc +s.charAt(i)); } Notice the differences in implementation, due to the lack of sequence operations we have in scheme (e.g. map). Here there is an element-by-element handling. 7