Laboratory Exercises For Computer Science 153

Introduction to Abstract Data Types

Goals: This laboratory exercise provides motivation for and a simple implementation of abstract data types. In subsequent labs, the concept of abstract data types will lead to an alternative approach to problem solving, called object-oriented programming.

Consider the following:
Problem: On-line telephone directories are to be created for several departments. For each directory, one should be able to add names, delete names, correct a telephone number, look up an entry by name, and print the full directory.

Commentary: A natural data structure for a directory would be an association list. Data for each individual would be stored as a (name, number) pair, and these pairs would be stored together on a list.

Observation: Writing code will be simplified significantly if we can be sure that data will be in an association list. Over time, however, additional code may be written, or other applications may be run in the same environment used for a directory. In such circumstances, we may want to ensure that other code will not change the structure of an association list.

In computer science, one approach to help guarantee that data structures will not be altered by other applications or code is to encapsulate the data and restrict what programs can access that data. This perspective motivates the concept of an abstract data type.

An abstract data type or ADT consists of a collection of data together with operations on that data. Further, the data and operations are packaged together, in such a way that only the specified operations may have direct access to the data. Other programs must use the specified ADT operations whenever those programs want to retrieve, store, or modify data within the ADT.

A Simple Scheme Implementation: Within Scheme, the definition for a (very simple) telephone directory might be as follows:

(define directory
   (lambda init-list
      (let ((assoc-list      ;; the ADT's data
                   (if (null? init-list)
                        '()
                        (car init-list))))
           (letrec           ;; the ADT's operations
               ((add-name         ;; add names to directory
                   (lambda (name-entry)
                      (set! assoc-list (cons name-entry assoc-list))))
                (look-up          ;; find a name in the directory
                   (lambda (name)
                      (assoc name assoc-list)))
                (show             ;; show current state of directory
                   (lambda ()
                      assoc-list))
               )             ;; end of local definitions
           (lambda (op . parameters)
              (case op       ;; process external requests   
                  ((add)     (add-name parameters))
                  ((lookup)  (look-up (car parameters)))
                  ((show)    (show))
                  (else "unknown operation")
              )
           )
         )
       )
   )
)
Before considering this directory definition in detail, we review how such a definition could be used. The following declaration creates a directory for some Mathematics/Computer Science faculty:
(define math-dir  
       (directory '(("Arnold Adelberg"  4201)  ("Marc Chamberland" 4207)
                    ("Pamela Ferguson"  4661)  ("Eugene Herman"    4202) 
                    ("Charles Jepsen"   4203)  ("Emily Moore"      4205)
                    ("Thomas Moore"     4206)  ("Samuel Rebelsky"  4410)  
                    ("John Stone"       3181)  ("Henry Walker"     4208)  
                    ("Royce Wolf"   on-leave))))
  1. After copying the above definitions into Scheme, check how this ADT can be used through the following commands:
    (math-dir 'show)
    (math-dir 'lookup "John Stone")
    (math-dir 'lookup "George Apostle")
    (math-dir 'add "Chris Hill" 4556)
    (math-dir 'show)
    
    In each case, describe the results of the operation.

Commentary: We now examine the ADT structure and usage in more detail. Generally, ADTs may be implemented in Scheme using the following general structure:

(define ADT-name
   (lambda initialization
      (let ((local variables and data structures, using initialization))
          (letrec ((definition of ADT operations))
              (lambda (op . parameters)
                 (case op
                    (collection of operation names and calls to ADT operations)
                 )
              )
           )
       )
   )
)
An ADT is a template for storing data and processing requests. The initial lambda expression indicates the ADT will produce an logical data/operations entity when called. To allow an ADT to be initialized (e.g., with directory data), the main lambda expression has an optional parameter.

The structure specifies local variables (e.g., an association list) in the opening let statement. Since the variables are declared locally, data within the ADT cannot be accessed directly by outside code. This guarantees that access to ADT data must be accessed through the specified ADT operations.

Details of various operations are given within a letrec statement. Again, such operations are defined within the ADT, so they cannot be called or changed by outside. However, since the letrec is nested within the let, these local operations can access the ADT's local data directly.

Since a user will request various operations, an ADT involves a lambda expression which returns results for each request. More specifically, a user will specify an operation op when using the ADT, and this operation may involve parameters. The ADT uses a case statement to determine which operation is being requested and to respond appropriately -- using the locally defined procedures as needed.

With this general definition of an ADT, we create specific variables for individual instances of the ADT. In addition to the math-dir above, for example, we could define a physics directory as follows:

(define physics-dir
       (directory '(("Robert Cadmus"  3016)
                    ("William Case"   3014)
                    ("Mark Schneider" 3018)
                    ("Paul Tjossem"   4289))))
This definition uses the directory to create an initialized physics directory (the directory procedure is run with an appropriate association list of data, returning an ADT structure).
  1. Explain how this definition allows users to request operations to physics-dir.

  2. What would happen if physics-dir were created without initial data? For example, what happens with
    (define physics-dir (directory))
    (physics-dir 'show)
    
Jargon: In object-oriented programming, and ADT is called a class, and an individual instance of an ADT (e.g., math-dir or physics-dir) is called an object. To use an established object, we identify the object, the desired operation, and any required data.
  1. Explain why the test cases above (in step 1) produce the output you observed.

  2. As shown, the directory class does no error checking of any parameters -- this class only checks if the requested operation is not valid. Add appropriate error checking to the add-name, look-up and show procedures.
The given directory class only allows initialization, addition of a name, lookup of an entry by name, and printing of the full directory. The original problem, however, specified some additional operations as well.
  1. Add a "delete" operation to the directory class, which allows a user to remove an entry from the directory, based on the name field.

  2. Add a "change" operation which allows a user to correct the entry of an entry, given name in the current directory entry.

This document is available on the World Wide Web as

http://www.cs.grinnell.edu/~walker/courses/153.sp02/lab-adt.html

created March 26, 1998
last revised February 25, 2002 by Henry M. Walker
Valid HTML 3.2!
For more information, please contact Henry M. Walker at walker@cs.grinnell.edu.