Data abstraction

The general methodology of isolating the detailed definition of data objects from the procedures that use and act on the objects. We want some "glue" that holds together objects to combine them into a single abstracted object.

But what is data anyways?

Claim:
We can get by just fine with only one primitive data type: the function.

Case-in-point 1:
The only condition a rational number representation needs to satisfy is the following:
if we construct a rational number x from two integers n and d, then extracting numer and denom of x and dividing them should give the same result as dividing n and d, i.e,

numerdenom=nd
In general, we can think of data as defined by some collection of selectors and constructors, together with specified conditions that these procedures must fulfill in order to be a valid representation

Case-in-point 2:
In fact, the only condition that a pair needs to satisfy is that if we construct a pair from two inputs x and y, then the procedures get-first and get-second should return the original inputs. Lisp demonstration that data pairs can be implemented just by procedures:

(define (cons x y) 
	(lambda (f) (f x y)))

(define (get-first z) 
	(z (lambda (p q) p)))

(define (get-second z) 
	(z (lambda (p q) q)))

Case-in-point 3:
Church numerals
We can even represent the concept of zero, one, two... as a procedure that applies any given function f 0, 1, 2.... times. Furthermore, addition would simply correspond to composition etc

(define zero (lambda (f) (lambda (x) x)))
(define one (lambda (f) (lambda (x) (f x))))
(define two (lambda (f) (lambda (x) (f (f x)))))

(define (add m n)
  (lambda (f) (lambda (x) ((m f) ((n f) x)))))

(define (mul m n)
  (lambda (f) (lambda (x) ((m (n f)) x))))

Useful data abstractions

Hierarchical data structures (vertical abstraction)

Data abstraction “stacks”, i.e, we can choose different implementations at every level without affecting other levels as long as the abstraction interfaces (procedure interfaces) are preserved.
Pasted image 20230722111749.png

Multiple representations (horizontal abstraction)

We might want to use different representations for the same data interchangeably.
Pasted image 20230722143226.png

Many strategies possible to handle the resulting code complexity.

Coercion helps deal with mixed data types even when code has only been written for different data types separately.

(define (apply-generic op . args)
  (let ((type-tags (map type-tag args)))
    (let ((proc (get op type-tags)))
      (if proc
          (apply proc (map contents args))
          (if (= (length args) 2)
              (let ((type1 (car type-tags))
                    (type2 (cadr type-tags))
                    (a1 (car args))
                    (a2 (cadr args)))
                (let ((t1->t2 (get-coercion type1 type2))
                      (t2->t1 (get-coercion type2 type1)))
                  (cond (t1->t2
                         (apply-generic op (t1->t2 a1) a2))
                        (t2->t1
                         (apply-generic op a1 (t2->t1 a2)))
                        (else
                         (error "No method for these types"
                                (list op type-tags))))))
              (error "No method for these types"
                     (list op type-tags)))))))

Links

Sources