Author's note written whilst HTML'izing this stuff....
Please remember that the stuff below was written prior to 1990 and hence in a different age of the world. The computer world has changed radically since then, thus this chapter should be regarded as a quaint history. Many things found in OopCdaisy now come free in GNU gcc. Many of the aims of OopCdaisy I have carried forward into C++ in the form of the LIBTREE library.
While writing the raster to vector program, the I was concerned with several Software Engineering issues. An Object Oriented Programming System, called OopCdaisy, was built on top of the C programming language. The aims of OopCdaisy are to gain :-
call TELL(%VAL self, %VAL display)
OopCdaisy is a suite of VAX C programs aimed at achieving the above ideals. As the Operating System and language on which OopCdaisy is piggy backed are fundamentally flawed in many of the respects mentioned above, only progress and not success is achieved.
The raster to vector conversion routine was built around OopCdaisy and much of its flavour has been carried through. Objects may be viewed as actors, and can be "told" to do things. This results in a slightly cute phrasing when talking about objects. For example, the phrases "Tell an object to do...", or "The object copies itself out to disk and then commits suicide."
It is well known that the debugging and maintenance phases are the most costly parts of the program development cycle. Experience with this program demonstrated that the time and effort in developing OopCdaisy was well rewarded in terms flexibility and reusability of code already developed and in terms of ease of debugging due to error checking and robustness of the system. Further benefits from the objects developed for this program are expected in the future, as many of the objects are quite generally applicable.
I like to think of OOP in terms of an Actor language. All the computer is a stage, and all the objects are merely actors therein. Objects are independent entities with their own memory, and own characteristic way of doing things. These characteristic ways of doing things are called methods. Actors can tell another Actor to do something, (to use one of its methods.)
Objects can as Actors do, have children. And these children have similar memories and similar ways (methods). I.e. The child object inherits the memory, (but not the contents), and methods of the parent. The child can then be changed so as to be slightly different from the ancestor.
When we tell a person to do something, we are not concerned with how he does it, only with what is done.
The "Actor allegory" sets the scene, the paradigm in which we are working, but it must be admitted that the actuality doesn't fit it very well.
The actuality derives more from placing a level of indirection between the data and the use of the data. The major gains from this level of indirection are implementation hiding, Inheritability and Polymorphism. To explain what is meant by these three terms, consider the following two examples.
As an illustrative example, consider points and lines in R2. One can represent them in cartesian coordinates (x, y) or polar coordinates (r,). One can represent lines as y-intercept and slope (c, m) or parametrically as a quadruple (x0,y0,dx,dy) where x(t) = x0 + dx * t and y(t) = y0 + dy * t. But whatever the representation, we can speak about the distance between two points, the distance from the origin, the perpendicular distance between a point and a line, and the intersection point between two lines.
In a program dealing with points in R2, such as a raster to vector conversion program, the logic of the program is concerned with points, lines, intersections and distances and could be programmed in any representation. Some representations are more convenient in some applications than others. Polar coordinates are very useful if you are mostly interested in angles between lines and distances from the origin, but add unwanted complexities when working with lines.
Central to OOPS is that the internal representation of the data becomes separated from the use of the data. Thus programs using an object are protected from the internal complexities of and changes in the implementation of an object.
Thus in the paradigm we would call the internally stored floating-point numbers the memory of the object, and the functions that calculate things about the object, the methods of the objects.
You may note in the discussion of representations above that in some respects the points and lines were similar. The point was represented by (x,y) and the line by (x0,y0,dx,dy). Indeed (x0,y0) can be said to be the starting point of the line. All methods that could possibly apply to a point could equally well apply to the starting point of a line. So in this respect a line descends from a point, in that a line's "memory" is a superset of the "memory" of a point, and all the methods of the parent apply to the child. This process is called inheritance.
Now consider a bag actor. A bag actor holds things. Anythings. Consider the display method. If you tell a line to display itself, it would call upon its display method to draw an appropriate line on the screen. If you tell a point to display itself it would call upon its display method to draw a dot in the right place on the screen. If you told a fluffy bunny to display itself, it would call upon its display method to draw a fluffy bunny on the screen. If you tell a bag to display itself, it would call upon its display method which would draw a picture of a bag on the screen, and then tell everything within the bag to draw itself. Thus if the bag contained a point, a line and three fluffy bunnies, you would get a bag, a point, a line and three fluffy bunnies on the screen. This process is called polymorphism, and is a marvellous labour saving device. Instead of having very intelligent plastic bags, that can draw a picture of everything, you just need ordinary dumb brown paper bags that can draw themselves and tell everything within them to draw themselves.
What if there was a bag inside the bag? No problem, the whole thing is quite recursive.
So with the above mental picture in mind, we proceed to lose ourselves in detail.
First we must clear up two points :-
Actors do not multiprocess. In life one person can do something while another is still busy. On the stage and in OOPS, one actor holds a pose while the other plays his part.
The memory of an actor is just a group of ordinary computer variables.
Secondly we should answer the question...
OopCdaisy is a set of C functions that maintain, manage and query a registry of object types and methods. The registry contains the following information on each object type :-
The registry also contains a list of all method names and a 2D array called the Virtual Method Table or VMT.
For every object type and for every method in the method name list there is a cell in the VMT that either contains nothing, or a pointer to a function that will execute the method on a object of that particular class.
In OopCdaisy an object is a C structure, (the C equivalent of a Pascal record). The memory of the object/actor is the data stored in the structure. The methods of the objects are ordinary C functions.
A major point on which OopCdaisy deviates from most OOPS implementations is the idea that each method is global across the whole system. This is polymorphism taken to the extreme. Every object type has an entry in the Virtual Method Table for every method available to every object class in the entire system, whether ancestor or not. In Turbo Pascal 5.5 by Borland for instance, only those methods of a class and its direct ancestors appear in the VMT.
The rule governing OopCdaisy is :- "If two methods have the same ASCII name then they do 'similar' sorts of things." What are the implications of this?
The disadvantage of course is that the VMT must be large. In fact the maximum number of object classes times the maximum number of methods times the sizeof each slot.
One of the central activities engaged in by an OOPS programmer is to add objects into containers. This conflicts quite severely with a structured programmer's habit of declaring local variables. If you declare an object local to a procedure and then add it into a container created at a higher level, as soon as you exit the low level procedure there is a "garbage" object within your container! When the program goes into any lower level procedure, the stack grows and the "garbage" object is overwritten. If you have full control over your compiler you may conceivably implement your procedure termination code to include instructions to all local objects to remove themselves from all containers and then die tidily. (The usual termination code is just to drop the stack pointer by the size of the local variables.)
A simpler and possibly more useful approach is to place a global ban on local objects. If you want an object, you can declare a local pointer to an object, and then allocate the object dynamically on the heap. This is the approach OopCdaisy takes.
Of course it may happen that some objects created at a local level may be lost, i.e. allocated on the heap, but not accessible as there are no pointers to it. There are two views of what has happened :-
To declare a class of objects one must declare a typedef of a struct, the first element being of the parent type. (If you speak Pascal, declare a record type.) For example, the type declaration for the points object is as follows :-
typedef struct { objects ancestor; double x, y; } points;
The objects type is defined in an OopCdaisy standard include file "oopDir:base.h" as :-
typedef struct { objectTypes type; } objects;
OopCdaisy identifies each class by a number that is an index into the class register and the Virtual Method Table. This number is stored in the class id variable, which for the point object type would be declared as follows :-
objectTypes pointType;
The person who implemented the point object would have stored these declarations in an include file e.g. "oopdir:2d.h". These declarations would be included into the users program and into the file which implements the class.
Before you can use a class of objects you must first inform OopCdaisy about the existence and nature of the class.
Before we can use a class of objects, we have to inform the OopCdaisy system that this class exists, and what methods this class has. This is done by calling the class's registering function. The class registering function is part of the implementation of the class. For example, if you wish to use the point object class, you would call the :-
register_point();
function. The register_point function typically would have been implemented like so :-
void register_point() { /* First tell OopCdaisy about the type we are registering, how many bytes of memory each instance uses, which object is its parent type. OopCdaisy finds a slot for the type and returns the slot number in class id variable pointType. OopCdaisy also copies all the parents methods into the VMT for pointType. So all methods available to the base type are also available to point! */ register_type( "point", &pointType, "base", sizeof( points)); /* Then OopCdaisy must be told all about additional methods that are available to the pointType. The "init" methods initialise a newly created object. Note that the init method is already available to the base type, but by registering init here we force OopCdaisy to use point_init to init point objects. OopCdaisy returns the slot number of the method in the method variables init and distanceSq. */ register_method( pointType, "init", &init, &point_init); register_method( pointType, "distanceSq", &distanceSq, &point_distanceSq); . . . }
OopCdaisy provides an assembly language function called new, which one can call to allocate and initialise an instance of an object.
New is a function returning a pointer to an object. The caller must tell new which type of object to create by passing a class id variable. The second parameter is a initialization method id variable, and the remaining parameters are parameters passed on to the initialization method.
objectPtr = new( objectType, method, p1, p2, p3,...);
So to create an object, first declare a pointer to the object you want to create, then call new. For example, to create a point object :-
main() { points * point; double x, y; . . . point = new( pointType, init, x, y); . . }
What has happened here? C calls the assembly language function new(), which performs the following actions :-
The init method stores x and y in the next two elements of the allocated space. If the chosen representation was polar coordinates, the init method could have converted the (x,y) to (r,) and then stored r and in the first two elements of the storage space.
To find the distance between two objects one can either call the distance function directly :-
distanceSq = point_distanceSq( point1, point2);
This is called a static method invocation, as the method invocation is done at compile time and can not be changed thereafter.
Or :-
distanceSq = tell( point1, distanceSq, point2 );
This is called a virtual method invocation. What happens here is the assembler routine tell takes its first argument to be a pointer to an object. It then verifies that this pointer points to an accessible part of memory. And that there is a valid object at that location. From this location it takes the object type. Tell takes the second argument to be a method identifier.
This is where the flexibility arises. The decision as to which function is actually called is only made when the 'tell' function is executed, and NOT before.
Now we must be at least partially aware of several distinctions here :-
Thus tell finds the address of the function point_distanceSq by looking up in the Virtual Method Table at
VMT[ objectType][ method]
and passes the remaining parameters to this function.
Because I cannot build OopCdaisy into the compiler, these curious distinctions that are not normally forced upon ones attention, must be made. In commercial OOPS systems such as Turbo Pascal, these distinctions exist, but are hidden from the user. However, as I will show in the section on Turbo Pascal, these subtleties are not necessarily a weak point of OopCdaisy.
Now we have had a glimpse of OopCdaisy sufficient to continue the discussion. A closer look and more on the programming philosophy of OopCdaisy is given in the annotated example in Appendix 1.
One of the main selling points of the OOPS approach is the ability to create truly flexible and reusable libraries of objects. So what does OopCdaisy offer?
Stacks with extensions to :-
The next major class is the direct descendant of the stack called a container. Thus all methods applicable to stacks apply to containers as well. The main feature of containers is that they can have index objects attached to them. Thus you can create an index object and attach it to a container. The index object can be marched up and down the container, objects can lifted out of the container via the index, objects inserted into the container before or after the index, objects can be found via the index etc. The main point about using an index object is it is safe. The programming to move up and down is done, debugged, and overruns are guarded against. Containers are aware of which indices are attached. Thus if several indices are attached to a container, and an object is removed from the container, all indices are checked to see if any index is left pointing at a non-existing object. If a container is deallocated, the container checks that all index objects are detached.
Two dimensional objects such as points, lines, arcs and circles are implemented with many useful methods such as distance between, intersection, etc. Half-planes or linear inequalities are implemented as being "to-the-left-of" directed lines.
Other object classes include LIFO lists, LISP style lists, and binary trees.
In theory I should give a large and scholarly literature review of all sources which influenced the design of OopCdaisy. Unfortunately in practice I find this extremely difficult. Many of the ideas were accumulated during a decade of omnivorous reading, the vast majority of this time I had no intention of writing such a system. Thus I did not collect the references. OopCdaisy just "happened" because I urgently needed decent list handling facilities, and I didn't want to have to keep rewriting these facilities. While I can "after the fact" produce a list of references from the library, it is somewhat unfair in the sense that it wasn't those papers that lead to OopCdaisy.
However, some sources such as Turbo Pascal 5.5 from Borland [Borland 1989], Smalltalk from Xerox [Mevel & Gueguen 1987], [Goldberg 1984] and the Oberon system [Wirth & Gutknecht 1989] can be identified and discussed, and comparisons can be made with other OOPS languages such as Eiffel [Meyer 1990].
A major prompt in starting work on OOPS was the arrival of Turbo Pascal 5.5 from Borland. Borland had grafted OOPS fairly neatly onto their Pascal compiler. However I consider Turbo Pascal 5.5 to be severely limited in the scope of its OOPS on the following grounds :-
The other source of ideas for OopCdaisy was Niklaus Wirth's Oberon system. From Oberon came the idea that the system should have the duty of memory policing. Oberon does garbage collection based on information in the object registry. OopCdaisy merely does memory accounting, thus enabling the programmer to find out which objects have not been deallocated.
To quote the BYTE may 1985 issue [Webster 1985], 'the influence of SmallTalk-80...has become just about legendary.' Indeed I regret that most of the influence of Smalltalk on OopCdaisy was via the BYTE August 1981 issue on Smalltalk, written some 9 years earlier than OopCdaisy. Thus Smalltalk's influence was more on the basis of half-remembered legend than on fact. The 'actor' paradigm came from Smalltalk. Had I remembered more I would surely have made OopCdaisy self-consistent in the sense of making the object types Smalltalk-like metaclass objects. Indeed the implementation of OopCdaisy registry is entirely in the classical pre-OOPS C programming style.
OopCdaisy would never have occurred if Eiffel and the Eiffel libraries had been available at the time of writing OopCdaisy. Eiffel as described in [Meyer 1990], achieves all the goals I set out for OopCdaisy. Particular points that are in Eiffels favour are :-
With reference to the problem with strictly typed OOP languages that was mentioned in the discussion of Turbo Pascal, [Meyer 1990] is inconclusive. His discussion on the subject of static versus dynamic typing favours static typing "whenever possible". However the rest of the text makes it unclear whether there exists a means of guardedly performing a type conversion, or a bug in the current compiler makes it is possible to bypass static typing!
The rapid growth of OOPS is not only due to the buzz word driven nature of the computer industry, but due to the virtues of the OOP listed at the start of the chapter.
However, I feel OopCdaisy has some valid points to make about OOPS in the future.
Previous | Next | ContentsError processing SSI file |