[comp.object] REPOST: The essence of objects...

cook@hplabsz.HPL.HP.COM (William Cook) (08/09/90)

I sent this out a couple of weeks ago, and then complained that
nobody replied (even privately!).  It seems that it didn't make it
to a few sites, so here it is again.  Thanks to everybody who has 
written me since...
I was worried that everything was going to the dev/null.
--------------------------------------------------------------------------

I gave a presentation on the essence of object-oriented programming at the
school/workshop on the foundations of object-oriented languages (REX/FOOL)
this summer.  The proceedings will be printed as a Springer volume this
fall.  In summary, I contrast object-oriented programming (which I prefer to
call procedural data abstraction (PDA)) with abstract data types (ADT).
They are not the same.  I can only illustrate the dichotomy here, and give
some references.

Here is an ADT for integer lists, written in ML (it would look about the
same in CLU, Ada, Modula-2, etc).

    exception error;

    abstype intlist = NIL | CELL of int * intlist with
        val xnil = NIL;

        fun null(l : intlist) = case l of
                NIL => true |
                CELL(x, l) => false;

        fun head(l : intlist) = case l of
                NIL => raise error |
                CELL(x, l') => x;

        fun tail(l : intlist) = case l of
                NIL => raise error |
                CELL(x, l') => l';

        fun cons(x : int, l : intlist) =
                CELL(x,l);
        end;

   head(cons(4, nil)) --> 4

This is called an "abstract data type" because the actual definition of
intlist is hidden, or abstracted, in the the rest of the program.

Here's an OOP version, written in a syntactically sugared version of ML (a
correct implementation is given in the appendix).

    datatype IntListObject = {
        null: bool,
        head: int,
        tail: intlistobject,
        cons: int -> intlistobject
        };

    let MakeNil : IntListObject =
          recursive self = {            -- the Nil "class"
            null = true,
            head = raise error,
            tail = raise error,
            cons = fn y => MakeCell(y, self)
            }
    and MakeCell(x : int, r : IntListObject) : IntListObject =
          recursive self = {        -- the Cell "class";
            null = false,           -- x and r are "instance variables"
            head = x,
            tail = r,
            cons = fn y => MakeCell(y, self)
            }

This program does not have any hidden or abstract types.  The full
definition of IntListObject is visible everywhere.  It uses procedural
abstraction to get information hiding.  Creating a list with element 4 and
then returning it works like this:

   MakeNil.cons(4).head  --> 4

As you can see, all of the NIL cases from the ADT are collected into the
MakeNil object, and all of the CELL cases are collected into MakeCell.
There are no case statements in the OOP version;  case statements are bad
because you are always having to add new cases (in this situation, if you
add a new representation).  This organizational difference has great
consequences for programming methodology.  For example, consider adding an
"interval" class that represents the integer list [n...m].  In the OOP
format its easy:

    MakeInterval(x : int, y: int) : IntListObject =
        if x > y then           -- the Interval "class";
            MakeNil             -- x and y are "instance variables"
        else
            recursive self = {
                null = false,
                head = x,
                tail = MakeInterval(x, y+1),
                cons = fn y => MakeCell(y, self)
                }

Exercise: consider adding this to the ADT code.

In short: Object-oriented programming is using procedures to represent data.
That is, procedural data abstraction.

-william cook@hplabs.hp.com


REFERENCES:

Early definition and discussion of virtues of "OOP":
    @article{Zilles73
    ,title="Procedural Encapsulation: A Linguistic Protection Mechanism"
    ,author = "Stephen N. Zilles"
    ,journal = "SIGPlan Notices"
    ,pages = "142-146"
    ,volume = 8
    ,number = 9
    ,year = 1973
    }

Change of direction, away from OOP, to invent ADTs:
    @article{LiskovZilles74
    ,author = "B. Liskov and S. Zilles"
    ,title = "Programming with Abstract Data Types"
    ,journal = "SIGPlan Notices"
    ,year = "1974"
    ,volume = "9"
    ,number = "4"
    ,pages = "50-59"
    }

Comparison of OOP and ADT, summarizes most significant issues:
    @incollection{Reynolds78
    ,author="John C. Reynolds"
    ,title="User Defined Types and Procedural Data Structures as
             Complementary Approaches to Data Abstraction"
    ,booktitle="Programming Methodology, A Collection of Articles by IFIP WG2.3"
    ,publisher="Springer-Verlag"
    ,address=NY
    ,editor="David Gries"
    ,year=1978
    ,pages="309-317"
    ,note="Reprinted from S. A. Schuman (ed.),
             {\em New Advances in Algorithmic Languages 1975},
             Inst. de Recherche d'Informatique et d'Automatique,
             Rocquencourt, 1975, pages 157-168",
    }

Discussion of OOP in chapter on "data-oriented program", similar to CLOS:
    @book{AbelsonSussman85
    ,author = "Abelson and Sussman"
    ,title = "The Structure and Interpretation of Computer Programs"
    ,publisher = "MIT Press"
    ,year = 1985
    }


APPENDIX: ML version of OOP code

    datatype intlistobject = tag of {
        null: unit -> bool,
        head: unit -> int,
        tail: unit -> intlistobject,
        cons: int -> intlistobject };

    fun MakeNil() =
          let fun self() = tag({
            null = fn() => true,
            head = fn() => raise error,
            tail = fn() => raise error,
            cons = fn y => MakeCell(y, self())
            })
          in self() end
    and MakeCell(x,r) =
          let fun self() = tag({
            null = fn() => false,
            head = fn() => x,
            tail = fn() => r,
            cons = fn y => MakeCell(y, self())
            })
          in self() end;