MASSACHVSETTS INSTITVTE OF TECHNOLOGY

Department of Electrical Engineering and Computer Science
6.001 -- Structure and Interpretation of Computer Programs

February 25, 1997

Lecture Notes

Reminder: Quiz One is on Wednesday, March 19, 5pm xor 7pm, Walker 3rd floor Gym.

Abstract Data Types

For our purposes, we can say that a data type is defined by the operations that can be applied to objects of the given type. In Scheme, all of the built-in types (booleans, strings, numbers, procedures, pairs, and others introduced later in the course) are first-class. That is, all objects in Scheme can be...

When we create our own data type, we provide operations that are intended to work only on objects of the type we are creating. Often, we can classify these procedures into the following categories:

Lists as Conventional Interfaces

Today's example falls in two parts: the n-dimensional point data type from the last lecture (implemented in terms of Scheme lists), and the implementation of lists in terms of pairs.

; The "n-dimensional point" data type, implemented as lists of numbers

(define make-point list)

(define (point.dimension pt) (length pt))
(define (point.coord pt dim) (list-ref pt (- dim 1)))

(define (point.x pt) (point.coord pt 1))
(define (point.y pt) (point.coord pt 2))
(define (point.z pt) (point.coord pt 3))

(define (point.to-origin pt)
  ;; Distance to origin
  (sqrt (add-up (map square pt))))

(define add-up (lambda (l) (accumulate 0 + l)))

(define (inside-unit-sphere? point)
  ;; Inside or on surface of unit sphere
  (<= (point.to-origin point) 1))
For much of the lecture we will deal with a standardized use of pairs: lists (in other languages these are called "singly linked lists"). There are many common procedures for manipulating this data structure. Some of them are already built into Scheme or MIT's implementation of Scheme. Here are a few that are used to implement our n-dimensional points:
; Lists implemented using pairs

(define (list? l)
  ;; LIST?: (any) -> Boolean
  (or (null? l)
      (and (pair? l) (list? (cdr l)))))

(define (length l)
  ;; LENGTH: (X*) -> Non-negative integer
  (if (null? l)
      0
      (+ 1 (length (cdr l)))))

(define (list-ref l n)
  ;; LIST-REF: (X*, Integer) -> X
  (cond ((null? l) (error "..."))
        ((= 0 n) (car l))
        (else (list-ref (cdr l) (- n 1)))))

(define (map fn list-of-values)
  ;; Returns list of same length as list-of-values
  ;;
  ;; MAP: (X->Y, X*) -> Y*
  (if (null? list-of-values)
      '()
      (cons (fn (car list-of-values))
            (map fn (cdr list-of-values)))))

(define (accumulate initial-value operation list-of-elements)
  ;; Returns a single value, built by using the OPERATION to combine
  ;; values from the LIST-OF-ELEMENTS.
  ;;
  ;; ACCUMULATE: (X, (Y, X)->X, Y*) -> X
  (if (null? list-of-elements)
      initial-value
      (operation (car list-of-elements)
                 (accumulate initial-value operation
                             (cdr list-of-elements)))))

(define (filter test list)
  ;; Returns a subset of the elements of LIST, consisting of those
  ;; that satisfy the TEST, in the order they appear in the LIST.
  ;;
  ;; FILTER: (X->Boolean, X*) -> X*
  (cond ((null? list) '())
        ((test (car list))
         (cons (car list) (filter test (cdr list))))
        (else (filter test (cdr list)))))

Standardizing on Lists

Lists are actually more deeply embedded in Scheme (and Lisp) than are other data types. For example there is special syntax in lambda and define to create a procedure that takes any number of arguments. This syntax allows the programmer to call a procedure by passing the arguments in a combination, but the procedure receives a list of the arguments. This is how procedures like list, +, and < are defined internally.
(define list (lambda elements elements)) ; LAMBDA notation
(define (make-point . coords) coords)    ; DEFINE notation
And there is a primitive procedure built in to Scheme, apply, that does the reverse: you give it a procedure and a list of arguments and it calls the procedure with those arguments:
(define x (list 1 2 3 4))
(apply + x) ==> 10
(apply * (list 10 9 8)) ==> 720
(define (point.to-origin pt)
  ;; Remember: pt is a list of numbers
  (sqrt (apply + (map square pt))))

Trees: Another use of Pairs

While Scheme has a conventional use of pairs to create lists, it has no single convention for representing trees. One way to make trees for use with our n-dimensional points is shown below. There are many variations, and there are significantly "more professional" ways to write this code. There is not, however, any clearly defined meaning for the word "better" in this context!
; A data abstraction for leaves of a tree: must be points in this
; case

(define leaf-tag "Leaf")

(define (make-leaf . coords)
  (cons leaf-tag coords))

(define (is-leaf? obj)
  (and (pair? obj)
       (eq? (car obj) leaf-tag)))

(define (leaf.point leaf) (cdr leaf))

; A data abstraction for non-leaf nodes

(define node-tag "Node")

(define (make-node dist outside on-or-inside)
  ;; MAKE-NODE: 
  ;;  (Sch-Num, (Node + Leaf), (Node + Leaf)) -> Node
  (list node-tag dist outside on-or-inside))

(define (is-node? obj)
  (and (pair? obj)
       (eq? (car obj) node-tag)))

(define (node.distance node)
  (list-ref node 1))
(define (node.outside node)
  (list-ref node 2))
(define (node.on-or-in node)
  (list-ref node 3))

; An operation that works on any kind of node: leaf or non-leaf

(define (distance node-or-leaf)
  (if (is-node? node-or-leaf)
      (node.distance node-or-leaf)
      (point.to-origin (leaf.point node-or-leaf))))

; Some operations on the tree itself

(define (tree.farthest node-or-leaf)
  (if (is-node? node-or-leaf)
      (tree.farthest (node.outside node-or-leaf))
      (leaf.point node-or-leaf)))

(define (tree.nearest node-or-leaf)
  (if (is-node? node-or-leaf)
      (tree.nearest (node.on-or-in node-or-leaf))
      (leaf.point node-or-leaf)))

(define (tree.valid? node-or-leaf)
  (if (is-node? node-or-leaf)
      (let ((smaller (node.on-or-in node-or-leaf))
            (not-smaller (node.outside node-or-leaf))
            (my-distance (node.distance node-or-leaf)))
        (and (tree.valid? smaller)
             (tree.valid? not-smaller)
             (< (distance smaller) my-distance)
             (>= (distance not-smaller) my-distance)))
        #T))