ch 1 recap: - used primitive data (numbers) and primitive operations (arithmetic) - combined procedures to form compound procedures through - composition - conditionals - use of parameters - saw that proocedures can be regarded as a pattern for local evolution of a process - classified, reasoned about, performed simple algorithmic analyses of common patterns for processes embodied in procedures - saw thath higher-order procedures empower us by allawing us to manipulate and reason about general methods of computation this is much of the essence of programming now, we will approach complex data. programs typically model complex phenomena, and often, "we construct computational objocts that have seweral parts in ordor to model real-world phenomena that have several aspects" in ch 1 we made compound procedures. in ch2 we will use compound data neat! coming up: - compound data object :: data "glued together" - data abstraction :: isolating the parts of a program that deal with how data is represented from the parts that deal with how data is used - abstraction barriers :: shielding a process from the more primitive details it deals with !! the distinction between data and procedure continues to blur at a breakneck pace - closure :: the glue for combining data works with primitive as well as compound data - conventional interfaces :: combining program modules in a mix-n-match fashion with compound data - symbolic expressions :: data whose elements can be arbitrary symbols - generic operations :: operations that can handle many kinds of data - data-directed programming :: a technique that allows individual data representations to be combined in isolation and combined additively - additively :: combining data representations without modification * 2.1 Introduction to Data Abstraction - data abstraction :: isolating how a compound data object is used from the details of how it's constructed - selectors and constructors :: the interface between a concrete data object and its abstract operations ** 2.1.1 Example: Arithmetic Operations for Rational Numbers suppose...we want to do arithmetic with rational numbers we want to add, subtract, multiply, divide, and test for equality let's assume the selectors and constructors are available as procedures: - (make-rat ⟨n⟩ ⟨d⟩) :: returns rational number with numerator n and denominator d - (numer ⟨x⟩) :: returns numerator of rational number x - (denom ⟨x⟩) :: returns denominator of rational number x we can happily use wishful thinking here. these procedures do not exist, but we can pretend they do and build a suite of funtionality: #+BEGIN_SRC scheme :noweb yes :session rats (define (add-rat x y) (make-rat (+ (* (numer x) (denom y)) (* (numer y) (denom x))) (* (denom x) (denom y)))) (define (sub-rat x y) (make-rat (- (* (numer x) (denom y)) (* (numer y) (denom x))) (* (denom x) (denom y)))) (define (mul-rat x y) (make-rat (* (numer x) (numer y)) (* (denom x) (denom y)))) (define (div-rat x y) (make-rat (* (numer x) (denom y)) (* (denom x) (numer y)))) (define (equal-rat? x y) (= (* (numer x) (denom y)) (* (numer y) (denom x)))) #+END_SRC we have operations based on the selector and constructor procedures - numer - denom - make-rat but these don't exist yet. we need a way to glue together two numbers to make a rational number *** pairs - pair :: a compound structure provided by lisp pairs can be constructed with the primitive procedure ~cons~ given a pair, we can extract the elements with ~car~ and ~cdr~ #+BEGIN_SRC scheme :noweb yes :session ccc (define x (cons 1 2)) (car x) #+END_SRC #+RESULTS: : 1 #+BEGIN_SRC scheme :session ccc (cdr x) #+END_SRC #+RESULTS: : 2 --> a note on terminology ~cons~ is short for "construct" the other two harken back to the IBM 704 implementation of lisp. the machcine had an addressing scheme that had references to the "address" and "decrement" parts of a memory location. ~car~ is short for "Contents of Address part of Register" ~cdr~ is short for "Contents of Decrement part of Register" cdr is pronounced "could-er" #+BEGIN_SRC scheme (define x (cons 1 2)) (define y (cons 3 4)) (define z (cons x y)) (car (car z)) ; 1 (car (cdr z)) ; 3 #+END_SRC !! the single compound primitive pair, implemented by the procedures ~cons~, ~car~, ~cdr~, is the only glue we need - list-structured data :: data objects built by pairs *** TODO representing rational numbers #+BEGIN_SRC scheme :session rats (define (make-rat n d) (cons n d)) (define (numer x) (car x)) (define (denom x) (cdr x)) (define (print-rat x) (newline) (display (numer x)) (display "/") (display (denom x))) #+END_SRC including a tostr method, we can use our new procedures for great fun #+BEGIN_SRC scheme :session rats (define one-half (make-rat 1 2)) (print-rat one-half) (define one-third (make-rat 1 3)) (print-rat (add-rat one-half one-third)) #+END_SRC this does not reduce the terms. we can do this if we have a gcd like back in 1.2.5 #+BEGIN_SRC scheme (define (make-rat n d) (let ((g (gcd n d))) (cons (/ n g) (/ d g)))) #+END_SRC ** 2.1.2 Abstraction Barriers there are abstraction barriers here ------|programs that use rational nums|------- rational numbers in problem domain ------------|add-rat sub-rat ... |------------ rational numbers as numers and denoms ------------|make-rat numer denom|------------ rational numbers as pairs ----------------|cons car cdr|---------------- however pairs are implemented ---------------------------------------------- these abstraction barriers isolate the various levels of the system. at each level, the barrier separates the programs above from the programs below that implement the abstractions. the procedures at each level are the interfaces that define the abstraction barriers and connect the different levels. changing from one representation to another should have minimal (none??!!) effects on the levels above. ** 2.1.3 What is Meant by Data? what is /data/, man? considering make-rat, for any int n and any nonzero int d, if x is (make-rat n d), this is true: : (numer x) n : ----------- = --- : (denom x) d data is defined by some collection of selectors and constructors with specified conditions that the procedures must fulfil in order to be valid. this pount of view can define high level data objects such as rats but also low level objects like pairs. the condition pair operations satisfy: - if z is (cons x y) - (car z) is x - (cdr z) is y we could implement pairs with no data structures, only procedures #+BEGIN_SRC scheme (define (cons x y) (define (dispatch m) (cond ((= m 0) x) ((= m 1) y) (else (error "argument not 0 or 1 -- CONS" m)))) dispatch) (define (car z) (z 0)) (define (cdr z) (z 1)) #+END_SRC this is an obscure but valid way to implement pairs. "procedural representations of data will play a central role in our programming repertoire." - message passing :: this style of programming will visit this in ch 3 re the issues of modeling and simulation ** 2.1.4 Extended Exercise: Interval Arithmetic Alyssa P. Hacker is building some cool shit the system will give ability to manipulate inexact quantites with known precision, such as with resistors. calculate parallel resistance: : 1 : Rp = ------------- : 1/R1 + 1/R2 alyssa postulates the existance of an abstract obj known as an "interval" with an upper and lower bound she presumes that given the endpoints of an interval, she can construct an interval with make-interval she writes a procedure for adding two intervals: #+BEGIN_SRC scheme (define (add-interval x y) (make-interval (+ (lower-bound x) (lower-bound y)) (+ (upper-bound x) (upper-bound y)))) #+END_SRC she also has a procedure for the product: #+BEGIN_SRC scheme (define (mul-interval x y) (let ((p1 (* (lower-bound x) (lower-bound y))) (p2 (* (lower-bound x) (upper-bound y))) (p3 (* (upper-bound x) (lower-bound y))) (p4 (* (upper-bound x) (upper-bound y)))) (make-interval (min p1 p2 p3 p4) (max p1 p2 p3 p4)))) #+END_SRC and the reciprocal, for division #+BEGIN_SRC scheme (define (div-interval x y) (mul-interval x (make-interval (/ 1.0 (upper-bound y)) (/ 1.0 (lower-bound y))))) #+END_SRC