Error processing SSI file

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.

2. OopCdaisy

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 :-

  1. Reliability through :-
    1. Thorough error checking, to prevent hidden errors.
    2. The availability of debugged sophisticated general data structures.
    3. Strict control and accounting of objects created and deallocated.
    4. Preventing use of deallocated objects.
    5. Ancestral type guards on parameters.
  2. Reusability through :-
    1. Polymorphism or genericity to share code.
    2. Inheritability.
  3. Flexibility through :-
    1. Separating the data structures from the program logic.
    2. Late binding.
    3. A global conception of object methods according to rule :- "If two methods have the same ASCII name then they do 'similar' sorts of things."
  4. Ease of debugging through :-
    1. Comprehensive error reports including
      1. Description of error, and parameters involved.
      2. Whether it is a user error or an internal logic error. If an internal error it reports internal to which module.
      3. In which routine the error occurred.
    2. Timeous error checking, finding the error when and where it occurs, not many lines later.
    3. Traceback of object and methods.
    4. Polymorphism enables one to tell any object including all objects in a container to display themselves. e.g. Using the VAX/VMS debugger :-

      call TELL(%VAL self, %VAL display)

  5. Robustness through :-
    1. Errors are allocated error numbers via the VMS message utility, thus making it easy to check and handle particular error classes.
    2. Errors are signalled as a standard VMS condition, thus the VMS condition handling facilities can be used to trap and handle errors.
  6. Practicality and cheapness - Ideally I would have preferred an operating system and language with all these features built in. One can buy products that partially address the above issues, but nothing that addresses all of them. To write a complete compiler and operating system was out of the question. Thus impoverishment of purse, dissatisfaction with current offerings, an overheated imagination, a bit of C code and a bit of assembler produced OopCdaisy, a object oriented program system piggy backed onto standard VAX C.

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.

2.1 What is object oriented programming?

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.

2.1.1 Representation hiding, and Inheritability - an example.

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.

2.1.2 Polymorphism - An illustrative example.

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.

2.2 How does OopCdaisy implement objects?

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...

2.2.1 What is OopCdaisy?

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 :-

  1. The memory required to create an object.
  2. The ASCII name of the object type.
  3. The object's parent object type.
  4. The number objects of this type which are currently active.
  5. The number of objects of this type which have been created so far.

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.

2.2.2 What are OopCdaisy Objects and methods?

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.

2.2.3 The global conception of object methods.

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?

  1. You can tell any object to do any method. If that object class does not have such a method, a non-fatal information grade signal called "NOMETHOD" is raised.
  2. You can tell a completely mixed bag of objects to do something.
  3. you are guaranteed that when this mixed bag of objects does something :-
    1. the program is not going to crash because some object within the bag is does not know what to do.
    2. all the objects within the bag will be doing more or less what you would expect.

    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.

    2.2.4 Dynamic vs Static instances of Objects.

    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 :-

    1. Niklaus Wirth and J Gutknecht [Wirth & Gutknecht 1989] in their Oberon system would say the programmer no longer needs those objects anyway and then proceed to recover the memory automatically in a garbage collection phase. In fact the Oberon system assumes that a programmer is too error-prone and unreliable to be trusted when the programmer claims a data item is not needed.
    2. OopCdaisy agrees with Oberon that programmers are not to be trusted, but is more authoritarian and insists that the programmer has made a mistake. If the object still exists, the programmer probably still wants it, but has just forgotten where he put the thing. So to prompt the programmers faulty brain, OopCdaisy maintains an accounting system of objects created and deleted. It is the programmers responsibility to deallocate all created objects, but OopCdaisy will at least be so kind as to inform the programmer at any stage how many objects of each variety that he still needs to dispose of.

    2.2.5 How is a new class of objects created?

    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.

    2.2.6 How is a new class of objects registered with OopCdaisy?

    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);
    	.
    	.
    	.
    }
    

    2.2.7 How is an object created?

    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 :-

    1. Looks up in the registry to find out how much space is required by a point object.
    2. Allocates that much space on the heap.
    3. Stores the object type in the first element of this space.
    4. Looks up, (in the virtual method table), the address of the point class's init method, and then calls the point class's init method with parameters x and y.

    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.

    2.2.8 How is an object used?

    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 :-

    1. A method is a C function. The naming convention for a method is objectType_methodName. Thus for example the stack push method is defined by a function called stack_push.
    2. A method id is an ordinary C variable which contains an index into the Virtual Method Table. Thus for example, tell finds the stack push method by using the contents of the variable push as an index into the Virtual Method Table. At that location tell finds the address of the function stack_push.
    3. The name of a method is an ASCII string, for example "push". Each slot in the VMT corresponds to a method name. Thus you can tell a Volkswagen to "ClimbTrees", but you will just get the signal NOMETHOD.

    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.

    2.3 Other OopCdaisy objects.

    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 :-

    1. push and pop from the top and the bottom of the stack.
    2. Find objects on the stack.
    3. TellAll objects within the stack to do something.
    4. Rotate the stack until a chosen object is on top.
    5. Concatenate stacks.

    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.

    2.4 OOPS in the literature.

    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].

    2.4.1 Turbo Pascal 5.5

    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 :-

    1. You cannot "tell" a container full of objects to "do" a certain "method", as Turbo Pascal 5.5 does not have any "method" variables.
    2. Turbo Pascal strict typing can interfere with object inheritance. Suppose you had a "line" object which was a descendant of a graphical "figures" object. A common method would be "intersection" which would calculate the intersection point between the "self" object and some "other" figure object passed as a parameter. The intersection method would decide what type the "other" figure was and uses the appropriate method, (say lineIntersectsLine or lineIntersectsCircle), to calculate the intersection point. Although one can use the typeOf function, (basically the Virtual Method Table pointer), to decide which descendant of "figures" "other" is, Turbo pascal strict typing implies that there is no way of upgrading the "other" variable to the appropriate descendant type. In OopCdaisy the problem doesn't arise since C allows type conversions between pointers to incompatible types. (Although not type conversion between incompatible types themselves.) OopCdaisy safeguards the whole process by placing a type guard check at the start of every low-level method.
    3. Neither Turbo Pascal nor OopCdaisy allows multiple inheritance. The absolute simplicity of single inheritance storage schemes militates against multiple inheritance.
    4. Neither Turbo Pascal nor OopCdaisy go far enough. For improved reliability, all data types should be objects. For example one can put an integer into a container, but as the integer is not an object, the polymorphism is totally lost and a program crash is likely to occur.

    2.4.2 Oberon

    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.

    2.4.3 Smalltalk.

    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.

    2.4.4 Eiffel.

    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 :-

    1. Eiffel is a pure OOP system. All data types are objects. [Korson & Mcgregor 1990]
    2. Encapsulation of data is enforced. Access to an objects private variables can only be made via the objects methods.
    3. Eiffel is strongly typed, (statically typed), to aid the production of correct systems.
    4. Generic classes can be declared. I.e. class definitions can be parametrized. For example the generic class list[ T] are lists of type T. One can then declare a list[ integer] or a list[ POPUP_MENU] or whatever.
    5. Infix and prefix operator functions for easy reading of expressions.
    6. Abstract classes can be declared in the form of deferred classes. (OopCdaisy has this facility as a common ancestor type with unimplemented methods)
    7. Reliable Software components using a contractual model. The Eiffel libraries are a collection of software components that provide a contractually bound service to client routines. If the client undertakes to meet certain preconditions as stated in and automatically checked via assertion statements, the component undertakes to perform a specified task resulting in post-conditions again automatically checked by assertion statements.
    8. Classes may be grouped into clusters or frames. For example there is a suite of objects designed to perform windowing and menu manipulations that can be specialised via the inheritance mechanism for individual applications.
    9. Reliability through :-
      1. Persistence of objects "beyond the programmatic grave", removing I/O related problems.
      2. Automatic garbage collection for complex dynamic data structures.
      3. Disciplined exception handling providing graceful termination and recovery.
    10. Availability of CASE tools.

    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!

    2.5 OOPS in the future.

    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.

    1. Programs based on OOP systems like OopCdaisy, Oberon and Eiffel are intrinsically more reliable than standard programming systems.
    2. OopCdaisy in its implementation makes it clear that an OOPS system is in fact a Data-Base Management System. The database being managed is that of data fields and methods. Current OOPS offerings, (OopCdaisy included), are in the same infantile stage of development as the early hierarchical or network database systems. I think one can say that the dust has settled over the database conflict and relational databases have emerged as the victor. I would strongly advocate that someone, somewhere initiates a research project into implementing an OOPS system which is based on a true relational database. Indeed partial approaches have already been made in the limited sense of a Data Dictionary. For example the DEC Common Data Dictionary. [DEC CDD]
    3. Experience gained on the OopCdaisy project would suggest the Eiffel technology is fundamentally sound.
    Previous Next ContentsError processing SSI file