PPL Pairs, lists and data abstraction. Compound Data Until now: atomic, unrelated entities Now:...
-
Upload
theodora-hill -
Category
Documents
-
view
213 -
download
0
Transcript of PPL Pairs, lists and data abstraction. Compound Data Until now: atomic, unrelated entities Now:...
PPL
Pairs, lists and data abstraction
Compound Data
• Until now: atomic, unrelated entities• Now: organized into structures• Why?– Better conceptual level of design– Modularity– Maintenance– Code reuse
• Actually a new type in the signature
Data Abstractions in Scheme?
• Of course, but first we need to present 2 new data types in Scheme:– Pairs– Lists
Pairs
• Combines two data entities into a single unit• Scheme provides built in primitives:– Value constructor: cons– Selectors: car, cdr– Identify predicate: pair?– Equality: equal?
Pairs
> (define x (cons 1 2))> (car x)1> (cdr x)2> x(1 . 2)
“toString” of pair
Pairs
Each data entity can be anything!> (define y (cons x (quote a)))> (car y)(1 . 2)> (cdr y)'a> y((1 . 2) . a)
Recall that x is the pair (cons 1 2)
Pairs
Do not confuse (cons 1 2) with (1 . 2) !!
1st is a Scheme expression (syntax) and 2nd is the ‘toString’ of the value (same as lambda and procedure)
Pair Type
Type constructor: PairType of:• cons: [T1*T2 -> Pair(T1,T2)]
• car: [Pair(T1,T2) -> T1]
• cdr: [Pair(T1,T2) -> T2]
Church Numbers
• Defined recursively:– Zero is a Church number.– For a Church number c, S(c) is a Church number
• Symbolic representation of natural numbers:– Zero, S(Zero), S(S(Zero)),…
• So what does it have to do with us?– We can represent them using pairs!– Zero, (S.Zero), (S.(S.Zero)), …
Church Numbers using Recursive Pairs
;; Type: [Number -> Church-num](define church-num (λ (n) (if (zero? n) 'Zero (cons 'S (church-num (- n 1))))))
> (church-num 3)'(S S S . Zero)
;(cons ‘S (cons ‘S (cons ‘S ‘Zero))))
Church Numbers using Recursive Pairs
;; Type: Church-num -> Number(define church-val (λ (c-n) (if (eq? c-n 'Zero) 0 (+ 1 (church-val (cdr c-n))))))
Useful Example(define member (lambda (el pair) (cond ((and (member-type-test el (car pair)) (member el (car pair))) #t) ((eq? el (car pair)) #t) ((and (member-type-test el (cdr pair)) (member el (cdr pair))) #t) ((eq? el (cdr pair)) #t) (else #f))))
(define member-type-test (lambda (el pair-candidate) (and (symbol? el) (pair? pair-candidate))))
Lists
• Finite sequence <v1, …, vn> (v1 … vn)
• v1 is head, (v2 … vn) is tail• Value constructors: cons and list– Empty: (list)– Non-empty:• (cons head tail) – tail must be a list!• (list e1 … en)
List Implementation
• Lists are actually nested pairs!• The inmost item is an empty list• Printing form of list is nice:– ( <a1> <a2> … <an> )
Lists
• Both will create the same list:(list 1 2 3)
(cons 1 (cons 2 (cons 3 (list))))
Lists
• Selectors:– car: head– cdr: tail
• Predicates:– list?– null?– equal?
Examples
> (define one-through-four (list 1 2 3 4))> one-through-four(1 2 3 4)> (car one-through-four)1> (cdr one-through-four)(2 3 4)> (car (cdr one-through-four))2
Note on Pairs and Lists
• Pair and List types have same value constructor and selector
• Scheme can live with it because its dynamically typed
Visual Representation
• Empty list
• Non empty list ((1 2) 3 4):
3
1 2
4
Lists and Types
• Homogenous– Examples: (1 2 3), ((1 2) (3)) – LIST(Number), LIST(T), …
• Heterogeneous– Examples: (1 #f 3), ((1 2) 3)– LIST
Useful List Operations
car + cdr:> (define x
(list 5 6 8 2))
> (car x)5> (cdr x)(6 8 2)
> (car (cdr x))6> (cadr x)6> (cddr x)(8 2)
Selector: list-ref
nth element of a list:;; Type: [LIST * Number -> T](define list-ref (λ (l n) (if (= n 0) (car l) (list-ref (cdr l) (- n 1)))))
(define squares (list 1 4 9 16 25 36))
(list-ref squares 4)
Operator: length
(define length (λ (l) (if (null? l) 0 (+ 1 (length (cdr l))))))
(define squares (list 1 4 9 16 25 36))
(length squares)6
Iterative length
(define length (λ (l) (letrec ((iter (λ (l c) (if (null? l) c (iter (cdr l) (+ c 1)))))) (iter l 0))))
Operator: append
(define append (λ (l1 l2) (if (null? l1) l2 (cons (car l1) (append (cdr l1) l2)))))
(append (list 1 2 3) (list 3 4))
Iterative append ;; Type: [LIST * LIST -> LIST](define append (λ (l1 l2) (letrec ((iter (λ (l1 l2 c) (if (null? l1) (c l2) (iter (cdr l1) l2 (λ (app-cdr-l1) (c (cons (car l1) app-cdr-l1)))))))) (iter l1 l2 (lambda (x) x)))))
Constructor make-list
Builds a list of given with given values(define make-list (λ (l v) (if (= l 0) (list) (cons v (make-list (- l 1) v)))))
Using Lists to Represent Trees
• Unlabeled trees:– Empty tree ()– Leaf is just value– Non-empty tree – non-empty list
• Example: (1 (2 3)) is the tree:
Using Lists to Represent Trees
• How can we add data to non-leaf nodes? (i.e. labeled tree) : each node is also a list!
• ((1) ((2) (3)))• Now we can create labeled trees:• (1 (0) (3 (2) (4)))
Leaves Count
• Unlabeled tree(define count-leaves (λ (t) (cond ((null? t) 0) ((not (list? t)) 1) (else (+ (count-leaves (car t)) (count-leaves (cdr t)))))))
Type Correctness with Pairs and Lists
• cons and list are primitives (not special forms!)
• So we need more axioms.
Pairs
For every type environment _Tenv and type expressions _S,_S1,_S2:
Tenv |- cons:[S1*S2 -> PAIR(S1,S2)]
Tenv |- car:[PAIR(S1,S2) -> S1]
Tenv |- cdr:[PAIR(S1,S2) -> S2]
Tenv |- pair?:[S -> Boolean]Tenv |- equal?:[PAIR(S1,S2)*PAIR(S3,S4) -> Boolean]
Homogenous Lists
For every type environment Tenv and type expression S:Tenv |- list:[Empty -> LIST(S)]Tenv |- list:[S* ...*S -> LIST(S)] n >0Tenv |- cons:[S*LIST(S) -> LIST(S)]Tenv |- car:[LIST(S) -> S]Tenv |- cdr:[LIST(S) -> LIST(S)]Tenv |- null?:[LIST(S) -> Boolean]Tenv |- list?:[S -> Boolean]Tenv |- equal?:[LIST(S)*LIST(S) -> Boolean]
Heterogeneous Lists
For every type environment Tenv and type expression S:Tenv |- list:[Empty -> LIST]Tenv |- cons:[S*LIST -> LIST]Tenv |- car:[LIST -> S]Tenv |- cdr:[LIST -> LIST]Tenv |- null?:[LIST -> Boolean]Tenv |- list?:[S -> Boolean]Tenv |- equal?:[LIST*LIST -> Boolean]
Type Derivation Example(define first-first (λ (p) (car (car p))))
1. {p:T1} |- p: T1
2. { } |- car : [Pair(T2,T3) -> T2
3. {p: Pair(T2,T3)} |- (car p):T2
4. { } |- car : [Pair(T21,T31) -> T21
5. {p: Pair(T22,T32)} |- (car p):T22
7. {p: Pair(Pair(T21,T31),T32)} |- (car (car p)): T21
8. {} |- (λ (p)…): [Pair(Pair(T21,T31),T32) -> T21]
9. { } |- first-first : [Pair(Pair(T21,T31),T32) -> T21]
What About Data Abstraction (ADT)?
• An interface: separation between usage (client) and implementation (supplier)
• Supplier gives constructors and selectors (getters), and client uses them.
• Order of development:1. Client level2. Supplier level
ADT
• It’s a new type with a difference: type is semantic while ADT is syntactic
• Signature of constructor• Signature of operators• Rules of correctness (Invariants)
So Why Start with Pairs and Lists?
• Pairs and lists will be our implementation! The client will not know we used pairs and lists, she will just use the operations we give her!
• Example next slide
Binary Tree ADT: Constructors
Signature: make-binary-tree(l,r)Purpose: Returns a binary tree whose left
sub-tree is l and whose right sub-tree is rType: [Binary-Tree*Binary-Tree -> Binary-
Tree]Pre-condition: binary-tree?(l) and binary-
tree?(r)
Signature: make-leaf(d)Purpose: Returns a leaf binary-tree whose
data element is dType: [T -> Binary-Tree]
Binary Tree ADT: SelectorsSignature: left-tree(r), right-tree(r)Purpose: (left-tree <t>): Returns the left sub-tree
of the binary-tree <t>.(right-tree <t>): Returns the right sub-tree of the
binary-tree <t>.Type: [Binary-Tree -> Binary-Tree]Pre-condition: composite-binary-tree?(t)
Signature: leaf-data(r)Purpose: Returns the data element of the leaf
binary-tree <t>.Type: [Binary-Tree -> T]Pre-condition: leaf?(t)
Binary Tree ADT: PredicatesSignature: leaf?(t)Type: [T -> Boolean]Post-condition: true if t is a leaf -- constructed by make-leaf
Signature: composite-binary-tree?(t)Type: [T -> Boolean]Post-condition: true if t is a composite binary-tree --
constructed bymake-binary-tree
Signature: binary-tree?(t)Type: [T -> Boolean]Post-condition: result = (leaf?(t) or composite-binary-tree?(t) )
Signature: equal-binary-tree?(t1, t2)Type: [Binary-Tree*Binary-Tree -> Boolean]
Binary Tree ADT: Invariants
leaf-data(make-leaf(d)) = dleft-tree(make-binary-tree(l,r)) = lright-tree(make-binary-tree(l,r)) = rleaf?(make-leaf(d)) = trueleaf?(make-binary-tree(l,r)) = falsecomposite-binary-tree?(make-binary-tree(l,r)) = true
composite-binary-tree?(make-leaf(d)) = false
Binary Tree ADT: Client Level
;Signature: count-leaves(tree);Purpose: Count the number of leaves of ’tree’;Type: [binary-Tree -> number](define count-leaves (lambda (tree) (if (composite-binary-tree? tree) (+ (count-leaves (left-tree tree)) (count-leaves (right-tree tree))) 1)))
Binary Tree ADT: Client Level
• More examples in lecture notes.• Look at them.• Really, look at them.
Binary Tree ADT: Implementation (supplier)
;Signature: make-binary-tree(l,r)
;Type: [T1*T2 -> LIST]
;Pre-condition: binary-tree?(l) ; and binary-tree?(r)(define make-binary-tree (lambda (l r) (list l r)))
;Signature: make-leaf(d);Type: [T -> T](define make-leaf (lambda (d) d))
;Signature: left-tree(t);Type: [LIST -> T];Pre-condition: composite-
binary-tree?(t)(define left-tree (lambda (t) (car t)))
;Signature: right-tree(t);Type: [LIST -> T];Pre-condition: composite-
binary-tree?(t)(define right-tree (lambda (t) (cadr t)))
Binary Tree ADT: Implementation;Signarture: leaf-data(t);Type: [T -> T];Pre-condition: leaf?(t)(define leaf-data (lambda (t) t))
;Signarture: leaf?(t);Type: [T -> Boolean](define leaf? (lambda (t) #t))
;Signarture: composite-binary-tree?(t);Type: [T -> Boolean](define composite-binary-tree? (lambda (t) (and (list? t) (list? (cdr t)) (null? (cddr t)) (binary-tree? (left-tree t)) (binary-tree? (right-tree t)))
Binary Tree ADT: Implementation;Signarture: binary-
tree(t);Type: [T -> Boolean](define binary-tree? (lambda (t) (or (leaf t) (composite-b-t t))))
;Signature: equal-binary-tree?(t1,t2);Type: [T1*T2 -> Boolean];Pre-condition: binary-tree?(t1) and
binary-tree?(t2)(define equal-binary-tree? (λ (t1 t2) (cond ((and (composite-binary-tree? t1) (composite-binary-tree? t2)) (and (equal-binary-tree? (left-tree t1) (left-tree t2)) (equal-binary-tree? (right-tree t1) (right-tree t2)))) ((and (leaf? t1) (leaf? t2)) (equal? (leaf-data t1) (leaf-data t2))) (else #f))))
Binary Tree ADT: Invariants
Seems ok, but look:> (leaf? (make-leaf (list 5 6)))#t> (has-leaf? (list 5 6) (make-leaf (list 5 6)))#f
We have no way to distinct composite leaves from tree…
We need a better implementation: tagged-data!
Tagged Data ADTSignature: attach-tag(x,tag)Purpose: Construct a tagged-
data valueType: [T*Symbol -> Tagged-
data(T)]
Signature: get-tag(tagged)Purpose: Select the tag from a
tagged-data valueType: [Tagged-data(T) ->
Symbol]
Signature: get-content(tagged)Purpose: Select the data from a
tagged-data valueType: [Tagged-data(T) -> T]
Signature: tagged-data?(datum)
Purpose: Identify tagged-data values
Type: [T -> Boolean]
Signature: tagged-by? (tagged,tag)
Purpose: Identify tagged-data values
Type: [T*Symbol -> Boolean]
Binary Tree ADT: Implementation using Tagged-Data
;Signature: make-binary-tree(l,r);Type: [(LIST union T1)*(LIST
union T2) ; -> Tagged-data(LIST)];Pre-condition: binary-tree?(l) ; and binary-tree?(r)(define make-binary-tree (lambda (l r) (attach-tag (list l r) ’composite-binary-tree)))
;Signature: make-leaf(d);Type: [T -> Tagged-data(T)](define make-leaf (lambda (d) (attach-tag d ’leaf)))
;Signature: left-tree(t);Type: [Tagged-data(LIST) ; -> Tagged-data(LIST union
T)];Pre-condition: composite-binary-
tree?(t)(define left-tree (lambda (t) (car (get-content t))))
;Signature: right-tree(t);Type: [Tagged-data(LIST) ->
Tagged-data(LIST union T)];Pre-condition: composite-binary-
tree?(t)(define right-tree (lambda (t) (cadr (get-content t))))
Proving Invariants
Invariant: leaf-data(make-leaf (d)) = d:For expression d, eval[(leaf-data (make-leaf d))] = eval[d]eval[(leaf-data (make-leaf d))] ==>eval[(leaf-data (attach-tag eval[d] ’leaf))] ==>eval[(get-content (attach-tag eval[d] ’leaf))] ==>eval[d]
Tagged-Data Implementation
• Have you noticed that we didn’t implement the tagged-data ADT?
• That’s the whole idea! We are clients! We don’t have to know the implementation!
• But we’ll give it anyway…
Tagged-Data ADT Implementation;Signature: attach-tag(x,tag);Type: [Symbol*T -> PAIR(Symbol, T)](define attach-tag (λ (x tag) (cons tag x)))
;Signature: get-tag(tagged);Type: PAIR(Symbol,T) -> Symbol(define get-tag (λ (tagged) (car tagged)))
;Signature: get-content(tagged);Type: [PAIR(Symbol,T) -> T](define get-content (λ (tagged) (cdr tagged)))
;Signature: tagged-data?(datum);Type: [T -> Boolean](define tagged-data? (λ (datum) (and (pair? datum) (symbol? (car datum)))))
;Signature: tagged-by?(tagged,tag);Type: [T*Symbol -> Boolean](define tagged-by? (λ (tagged tag) (and (tagged-data? tagged) (eq? (get-tag tagged) tag))))
Type Inference with Type Constraints using ADT
• ADT:– Type expression (TE)– Substitution– Equation
• Client produces– sub-application, sub-combination
– Infer-type, solve
• Implementation– TE is tagged-data (tag is
type constructor)– Substitution is 2-element
list– Equation is 2-element
list
Type Inference with Type Constraints using ADT
• Full implementation in course site.• We describe part of it.
Type Expression ADT (partial)Constructors:Signature: make-proc-te(tuple-te, te)Type: Client view: [Tuple*Type -> Procedure]Example: (make-proc-te (make-tuple-te (list Number)) 'Number)==> (-> (*Number) Number)
Signature: make-tuple-te(te-list)Type: Client view:[LIST(TE)-> Tuple]Example: (make-tuple-te(list 'Number (make-proc-te (make-tuple-te (list 'Number))'T1)))==> (* Number (-> (* Number) T1))
Some getters:Signature: get-constructor(te)Type: Client view: [Composite-Type-Expression -> Symbol]Example: (get-constructor (make-tuple-te (list 'Number 'Number))) ==> *
Signature: tuple-components(te)Type: Client view: [Tuple -> List(TE)]Example: (tuple-components (make-tuple-te (list 'Number 'Number))) ==> (Number Number)
Signature: proc-return-te(te)Type: Client view:[Procedure -> TE]Example: (proc-return-te (make-proc-te (make-tuple-te (list 'Number)) 'T1)) ==> T1Some predicates:
Signature: equal-atomic-te?(te1 te2)Type: [LIST union Symbol * LIST union Symbol -> Boolean]Signature: type-expr?(te)Type: [T -> Boolean]
Procedures as Data
• So far we’ve seen only pairs and lists as compound data
• Procedures can be data too! And in more than one way
• What?! Procedures can be data?! Yes.
Pair ADT
• Suppose Scheme did not include the built-in Pair or List type
• Pair ADT– Define the ADT (cons’r, getters, etc)– Implement it using procedures(!!)
Pair ADT: Cons’r, Getters, Predicates…
Signature: cons(x,y)Type: [T1*T2 -> PAIR(T1,T2)]
Signature: car(p)Type: [PAIR(T1,T2) -> T1]
Signature: cdr(p)Type: [PAIR(T1,T2) -> T2]
Signature: pair?(p)Type: [T -> Boolean]
Signature: equal-pair?(p1,p2)Type: [PAIR(T1,T2)*PAIR(T1,T2) -> Boolean]
Pair ADT: Invariants
(car (cons x y)) = x(cdr (cons x y)) = y
Pair ADT Implementation I: Eager (also called message passing)
;Signature: cons(x,y);Type: [T1*T2 -> [Symbol -> (T1 union T2)](define cons (λ (x y) (λ (m) (cond ((eq? m 'car) x) ((eq? m 'cdr) y) (else (error "..." m) )))))
Not to confuse with
applicative-eval!
Pair ADT Implementation I: Eager
;Signature: car(pair);Type: [[Symbol -> (T1 union T2)] -> T1](define car (λ (pair) (pair ’car)))
;Signature: cdr(pair);Type: [[Symbol -> (T1 union T2)] -> T2](define cdr (λ (pair) (pair ’cdr)))
What Just Happened?
• A pair is a procedure that stores the information about the pair components
• The “magic”:– The substitution: the values are “planted” in the
procedure– The returned value is a procedure that is not
applied. car and cdr apply it.
applicative-eval[ (cons 1 2) ] ==>*<closure (m)
(cond ((eq? m ’car) 1)((eq? m ’cdr) 2)(else (error “…" m) ))>
applicative-eval[ (car (cons 1 2 )) ] ==>applicative-eval[ car ] ==> <closure (pair) (pair ’car)>applicative-eval[ (cons 1 2) ] ==>* <the cons closure>
sub[pair, <cons closure>, (pair ’car) ] ==> (<cons closure> ’car)
reduce:( (lambda (m)
(cond ((eq? m ’car) 1)((eq? m ’cdr) 2)
(else (error "Argument not ’car or ’cdr -- CONS" m) )))’car) ==>*applicative-eval, sub, reduce:
(cond ((eq? ’car ’car) 1)((eq? ’car ’cdr) 2)(else (error “…" ’car) )) ==>
1
Pair ADT Implementation II: Lazy
Lazy;Signature: cons(x,y);Type: [T1*T2 ; -> [ [T1*T2 -> T3] -> T3]](define cons (lambda (x y) (lambda (sel) (sel x y))))
Eager;Signature: cons(x,y);Type: [T1*T2 -> [Symbol -> (T1
union T2)](define cons (λ (x y) (λ (m) (cond ((eq? m 'car) x) ((eq? m 'cdr) y) (else (error "..." m))))))
Pair ADT Implementation II: Lazy;Signature: cons(x,y);Type: [T1*T2 -> [ [T1*T2 ; -> T3] -> T3]](define cons (lambda (x y) (lambda (sel) (sel x y))))
;Signature: car(pair);Type: [[ [T1*T2 -> T3] -> T3] -> T1](define car (lambda (pair) (pair (lambda (x y) x))))
;Signature: cdr(pair);Type: [[ [T1*T2 -> T3] ; -> T3] -> T2](define cdr (lambda (pair) (pair (lambda (x y) y))))
applicative-eval[ (cons 1 2) ] ==><closure (sel) (sel 1 2)>
applicative-eval[ (car (cons 1 2 )) ] ==>* applicative-eval[ car ] ==> <closure (pair) (pair (lambda(x y) x))> applicative-eval[ (cons 1 2) ] ==>* <closure (sel) (sel 1 2) >sub, reduce:applicative-eval[ ( <closure (sel) (sel 1 2) > (lambda(x y) x) ) ] ==>*applicative-eval[ ( (lambda(x y) x) 1 2) ] ==>applicative-eval, sub, reduce:1
Eager vs Lazy
Eager• More work at constructions
time. Immediate at selection time.
• Selectors that are not simple getters can have any arity.
Lazy• Immediate at construction
time. More work at selection time.
• Selectors can be added freely, but must have the same arity.