STRUCTURED PROGRAMMING

Dressing the Truth In Rubber Suits

Jeff Duntemann, K16RA

Remember AI? Sure you do. A couple of years ago, it was going to change the face of the planet, according to the legions of ignorant Esther Dyson wannabees selling $900 newsletters to corporate seatwarmers with lots of money to waste. AI was going to allow programs to configure themselves. AI was going to allow us to get our work done without expending any effort. AI was going to allow our software to read our minds. It got so bad that PC Week columnist Jim Seymour suggested that software vendors could double sales by slapping a gold starburst sticker on the outside of every product package reading, "New! Improved! With AI!"

Meanwhile, back in the trenches, the guys who were researching AI were sadly shaking their heads. They never made those promises, or any promises. They weren't trying to create an artificial human brain. They were just looking for new ways to arrange the same old instructions we've been fetching and executing all along. The human brain is a pretty successful computer, they reasoned, so why not try to learn something from the way it works? Had they called it cognitive modeling or somesuch, nothing dramatic would have happened. (Industry mavens rarely abuse what they can't pronounce.) But say "artificial intelligence" and, whammo! Isaac Asimov's robots come striding over the horizon, with Popular Mechanics, People Magazine, and finally The National Enquirer in hot pursuit.

You'll notice that nobody's talking much about AI anymore. The hypemongers buried it so deep that nobody in the mainstream software development community may ever take it seriously again. The hype machines have been quiet lately, but don't assume that they're gone -- they're just getting a valve-and-ring job so to be in top shape when the next fad happens by, like remoras watching for a passing grouper.

Lately, I've begun to hear them revving, this time over something with a lot more near-term potential than AI: Object-oriented programming. People who never said OOPs before, except when using chopsticks, have become self-anointed experts, and have begun dressing the truth in rubber suits.

So let's dump some sand in the hype machines, and nail some nascent conventional wisdom to the wall.

Rubber Truth #1: OOP Makes Coding Effortless

Dream on. Furthermore, OOP doesn't even make coding easy. One might argue that it makes coding more difficult, in that OOP requires considerable forethought and design effort up front.

An object hierarchy requires a starting point; a handful of abstract classes that exist to broadcast certain characteristic behavior down the many inheritance branches. The root of the tree is a foundation on which thousands or tens of thousands of lines of code may depend. If you discover halfway through a 50,000 line application that you've conceptualized the fundamental abstract classes wrong, you might just have to kiss 25,000 lines of code good-bye. So take heed:

Duntemann's OOP Warning #1: Invalidating the Root Invalidates The Leaves!

When you cast a program design in OOP terms, think very hard about the nature of your conceptual model. Once you've created a solid, correct, and workable foundation in terms of an object hierarchy, coding gets a little easier because you can inherit and reuse some of that hard, early general work in the later, more specific tasks. That doesn't mean that the total effort required to finish the project is going to be any less. It's just that the hardest work gets done up front, and the rest of it seems easy only by comparison.

Rubber Truth #2: OOP Makes Programs Bulletproof

I confess, I've stretched this one a little myself, and have been bitten in the hindparts for my trouble. At first thought, encapsulation might appear to make it harder to bugs to propagate beyond the bounds of the object in which they occur. Unfortunately, all method code is bequeathed to an object's children, for good or for bad. There's no software equivalent of Maxwell's Daemon sitting on a post at the object's interface, letting features pass down the object hierarchy but keeping the bugs behind. So, folks, keep in mind:

Duntemann's OOP Warning #2: Bugs Are Just as Inheritable as Features

It's true to an extent that languages that enforce encapsulation, such as Smalltalk and Actor, prevent a certain class of under-the-table bugs caused by directly referencing an object's state or instance variables. In Smalltalk and Actor, you simply cannot reference an object's internal state (that is, what in Object Pascal we would call its fields) directly. This makes bug propagation a one-way street down along the object hierarchy, which is better than nothing. Unfortunately, even this protection is missing from C++ and the various dialects of Object Pascal. Both Turbo Pascal and QuickPascal require that you, the programmer, enforce encapsulation, and if you're a bad enforcer, you're no better off than with traditional structured programming techniques.

Rubber Truth #3: OOP Allows Easy Free-form Prototyping, and You Can Keep The Prototype

This is one of those deceptive and infuriating assertions that in certain circumstances might well be right -- but for all the wrong reasons. I'm reminded of a pre-Lego construction toy I had when I was six called "Lock-A-Blox." It was a boxful of brightly-colored blocks, with slots on two faces and tabs on the two opposite faces. All the tabs fit all the slots, and all block dimensions were always even multiples of the smallest. Everything always fit together, no matter how you tried it. There were no wrong combinations, no embarrassing chinks or bulges where things didn't quite fit.

Nonetheless, no matter what you built with it, from the Brooklyn Bridge to the Eiffel Tower to Godzilla, your creation always looked like . . . a bunch of Lock-A-Blox.

Rapid free-form prototyping is indeed possible in Smalltalk and Actor. It's done by taking instances of the hundred-plus object types in the standard class library and hooking them together until things work. You stick menu B behind radio button A, and if they don't look quite right, well, pull them apart and try something else. There's no sense of loss in throwing away the menu because you didn't have to write the code to create the menu. It was all there in the class library, like bubble-packed cold meat hanging on pegs in the refrigerator case down at Safeway.

You can very quickly get a series of menus and other screen controls together in Smalltalk or Actor, and if the prototype user interface isn't too messy, you have a reasonable chance of filling in the guts without having to abandon and rewrite the user interface.

There's a price to be paid: Your application will be made out of pieces written by somebody else, and it will have a look and overall structure dictated by the shape of those pieces. This is not necessarily a bad thing, especially if you're sick of messing around with your own window manager code and you are looking for a standard user interface.

That, however, is for Smalltalk and Actor. There is currently no massive class library in QuickPascal, Turbo Pascal, or Zortech C++. In Object Pascal and C++ you have to build your own Lock-A-Blox, with all the time, effort, and torn hair that that implies.

Duntemann's OOP Warning #3: The Quality of an OOP Prototype Depends Utterly on the Quality of the Standard Class Library

The bottom line of this rubber truth is that the ease of prototyping has less to do with OOP than with a well-thought-out library of prefab program components. I've done some amazingly easy prototyping with Turbo Power Software's Turbo Professional 5.0, and with a superb Modula-2 library product called "Repertoire," all without object-oriented anything. OOP contributes a little -- objects are more independent than traditional procedures and functions, and thus, they combine better and with fewer possible side effects -- but far less than most people think.

Turbo Pascal prototyping will be made lots easier once Turbo Power's Object Professional library is finished and shipping. (This may well be the case now, while you're reading this; though, while I'm writing, they still have a ways to go.) And over time, such ambitious class libraries will become de rigueur in all OOP languages, just as they have been in Smalltalk and Actor from day one.

Object-oriented programming is no magic pill. It does not allow effortless programming of complex applications that work the first time and never break. It does not make applications run faster, but if badly used can make them run slower. (In fairness to OOP, this is also true of most every programming technique you could name.) Its benefits fall mostly to the programmer: Greater richness of expression in program design; greater potential to reuse well-designed program components; and greater ability to fix, change, and extend existing applications in a maintainable manner. From the outside, the programs will look exactly the same.

The Parent Trap

I treated polymorphism in detail back in November, but it may be time to come back and shed some light into some corners that have remained dark through it all. I lurk on CompuServe a lot, tallying the subjects people have trouble understanding -- that's an excellent place for professional explainers, like me, to get their raw material. And whereas the broad concepts of polymorphism seem to be coming across, the details keep getting scrambled and misconstrued.

Chief among these is the matter of polymorphic assignment compatibility. In an object hierarchy, assignment compatibility is extended along a given object class' domain. This domain includes the class and all of its descendant classes, as shown in Figure 1. The shaded classes are the domain of class Arc.

The rule is this: An object can be assigned to any type "rootward" in its domain. (Let's not argue about "above" or "below," because those words depend utterly on how you draw the hierarchy. In Figure 1, the root of the hierarchy is at the top of the page.) In other words, you could take an instance of type Arc and assign it to an instance of type Circle:

  MyCircle := MyArc;

Ordinarily, Pascal would call you down on type conflict grounds, and it still will, if you were to assign MyArc to something outside of its domain; say to an instance of type Rectangle or Polygon.

One thing to remember that people too often forget is that the reverse is not true: You cannot assign a parent to a child. This won't compile:

  MyArc := MyCircle;

Try to remember this key phrase: "To the general assign the specific." One way to think of an object hierarchy is as a movement from the most general abstract classes at the root to the most specific classes at the leaves. If you'd prefer an earthier mnemonic, try this: "You can hang a leaf on a root. You can't hang a root on a leaf." Think of assigning the specific to the general as hanging a leaf on the root.

Making the Compiler Swallow Polymorphism

The whole idea behind extending assignment compatibility rootward through an object's domain is to make polymorphism syntactically kosher to the compiler. In a sense, polymorphism is the hiding of different classes behind the mask of a common ancestor class. The mask hides the differences between the classes, and emphasizes those aspects the classes have in common -- in this case, the aspects defined in the common ancestor class.

In Figure 1, for example, Line, Rectangle, and Circle are all descended from Point. Point is literally all that the three have in common. Inside Point there is a pair of methods for displaying and erasing the object's graphic image: Show and Hide. Inheritance causes Show and Hide to be passed down into all of Point's child classes. The child classes, however, have the option of redefining their own specific methods under the names Show and Hide. A line is drawn differently from a circle, so Line.Show would necessarily be different from Circle.Show.

Nonetheless, the method name and the general idea (to display a figure on the screen) is the same for all the graphic object classes. So bring on the masks:

   VAR
   Figures : ARRAY[1..3]
   OF Point;
   ACircle : Circle;
   ARectangle : Rectangle;
   ALine : Line;

   . . .

   Figures[1] := ACircle;
   Figures[2] := ALine;
   Figures[3] := ARectangle;

   FOR I := 1 TO 3 DO
         Figures[I].Show;

Here we have an array of type Point, to which we assign instances of Circle, Line, and Rectangle as we choose. Because Point is within the domain of all three, extended assignment compatibility allows the compiler to accept these assignments, even though they violate traditional Pascal strong typing.

Polymorphism enters the picture in the FOR statement, where we step through the array, calling the Show method belonging to each array element. Even though the elements of the array Figures are all nominally type Point, late binding and polymorphism allow the Circle.Show method to be invoked for element 1, the Line.Show method for element 2, and the Rectangle.Show method for element 3. Without extended assignment compatibility, the three graphics figure objects could never have been assigned to the array, and the polymorphic method calls could never have happened.

The Parting of the Pascals

One (serious) catch. The syntax shown before doesn't work in Turbo Pascal. It compiles correctly, but polymorphism won't happen. In other words, if you compile the above code fragment under Turbo Pascal, the FOR statement will execute Point.Show for all three of its array elements -- as if the polymorphic assignments to the three descendent classes never took place.

This isn't a bug -- it's part of the spec. QuickPascal will compile and run code like the example shown earlier. The two Object Pascals differ significantly in their object philosophy (while agreeing in almost everything else), but the most fundamental difference between them is that all QuickPascal objects are allocated on the heap, whereas Turbo Pascal objects may be allocated either on the heap or (by default) in the data segment. Turbo Pascal objects allocated in the data segment (static objects) and addressed directly cannot take part in polymorphism.

Keep this in mind, always: Polymorphism is by nature a dynamic business, and is done through pointers. If an object is not accessed through a pointer, it won't polymorph.

So how, then, does QuickPascal manage to make the example code above run correctly? It's an aspect of the between-two-worlds nature of objects in QuickPascal. When a QuickPascal object is declared, it's declared statically, just as any variable is:

   VAR
          ACircle : Circle;

However, declaring a QuickPascal object is not enough. Before it can be used (actually, before it really exists) it must be allocated on the heap, via New:

   New(ACircle);

Thereafter, the object can be referenced as though it were a static variable:

   ACircle.Show;

No caret symbols here, even though ACircle exists entirely on the heap. In a sense, QuickPascal objects are neither fully static nor fully dynamic. They're just . . .well. . . objects, that's all.

Behind the scenes, an apparently static reference to a QuickPascal object really is a pointer reference. The name of a QuickPascal object is in truth a pointer to that object on the heap. They've just done away with the familiar caret or "up arrow" pointer reference notation.

In Turbo Pascal, by contrast, an object is either fully static and exists in the data segment, or is fully dynamic and exists on the heap and must be accessed through the traditional pointer reference notation. A Turbo Pascal object is best approached as a record with some special properties, and obeys all the rules a record would with respect to allocation and referencing.

Thus, when in QuickPascal, we assign object instances to an array of object instances, we're actually assigning object pointers to an array of object pointers:

   Figures[1] := ACircle;
   Figures[2] := ALine;
   Figures[3] := ARectangle;

Here, ACircle is actually a pointer to a block of memory on the heap containing the object's actual data and method table. Similarly, the Figure's array is in reality an array of pointers -- not whole objects. If we had intended to use the elements of Figures without assigning already-allocated objects to them, we would have had to allocate each individual element of Figures with the New statement:

   New(Figures[1]);
   New(Figures[1]);
   New(Figures[1]);

This static/dynamic duality of QuickPascal objects (and Apple Pascal objects before them) is a source of some serious confusion to newcomers. Best to remember: All QuickPascal objects are at the end of pointer references, whether it looks like they are or not!

Polymorphism Through Pointers

Fortunately, both QuickPascal and Turbo Pascal objects can be manipulated through explicit pointer references. And just as assignment compatibility of objects is loosened up within an object's domain, so is the assignment compatibility of pointers to those objects.

In other words, just as a Circle object may be assigned to a Point object, a pointer defined as a pointer to Circle may be assigned to a pointer defined as a pointer to Point:

   TYPE
         PointPtr = ^Point;
         CirclePtr = ^Circle;

   VAR
          CircleHandle : CirclePtr;
          PointHandle : PointPtr;

   . . . .

      Pointhandle := CircleHandle;

In this situation, polymorphism will work with either compiler, using identical syntax.

The best way to show polymorphism through pointers is with a complete program. There's no substitute for loading up a piece of code and watching it work before your eyes. Listings One and Two are pretty much the same program, implemented as their particular compilers demand. Listing One is the QuickPascal version, and Listing Two is the Turbo Pascal 5.5 version.

The programs implement two object classes: Father and Son. Son is a descendant of Father. Both have methods named Talk, which display appropriate text to the screen for each class. Father says one thing; Son says another. The main body of each program does three things:

    1. Allocates and initializes three objects, Dad (of type Father), Twerp (of type Son), and Person (also of type Father).

    2. Attempts polymorphism through direct references. This works for QuickPascal and fails for Turbo Pascal.

    3. Attempts polymorphism through pointer references. This works identically for both compilers.

Assigning a Son instance to a Father instance is done in both programs:

   Person := Twerp;
   Person.Talk;

The method invocation will call Son. Talk in QuickPascal and Father. Talk in Turbo Pascal. Why?

Person and Twerp are both pointers beneath the skin of QuickPascal, and assigning Twerp to Person only copies the Twerp pointer to the Person pointer. This is easy and fast -- all pointers are the same size, 32 bits.

In the Turbo Pascal version, on the other hand, Person and Twerp are both objects in the data segment. They are of different sizes. Assigning Twerp to Person means moving physical data from one location in the data segment to another. To avoid disrupting data beyond the bounds of Person, Twerp will have to be truncated during the move -- because Twerp is larger than Person. Data is lost, which is never a good idea.

If you look beneath the surface of the Turbo Pascal version, you'll find that data is moved from Twerp to Person, and that the Twerp.Girlfriend's field is lost in the move. Person retains its identity as a Father object, however, and any call to a method in Person will invoke Father's methods rather than Son's.

The key here is Person's link to the Virtual Method Table, which I described in some detail in my November column. When Twerp is assigned to Person, all of Twerp's data is copied over Person's, but the VMT links are not involved in the move. Person's VMT link remains intact, even after the assignment overwrites all of Person's data with Twerp's. Why? The VMT contains the size of its object, returned by the Sizeof function and used internally by other aspects of the run-time library. Especially when you're dealing with statically allocated data, you do not want to lose track of how big an individual item is. Get it wrong during something as simple as an assignment, and you overwrite adjacent data.

In short, static data is no place for polymorphic fooling around. It's much safer on the heap, where you can throw pointers around instead of whole objects.

Much better to do it this way:

   Link := @Dad;
   KidLink := @Twerp;
   Link := KidLink;
   Link^.Talk;

Here, Link is a pointer to Father object Dad, and KidLink is a pointer to Son object Twerp. Assigning KidLink to Link can be done because Link's referent shares KidLink's referent's domain. And this time, specifying Link^.Talk executes Son.Talk because polymorphism works through pointer references in both QuickPascal and Turbo Pascal. No data is moved; once the assignment happens, Link points directly to Twerp, so Link^.Talk executes Twerp's Talk method.

The Limits of Polymorphism

This is terrific stuff -- hiding the son behind a mask of the father. There's a catch: The only object methods or data accessible through polymorphic assignment are those that the two involved classes have in common. This is subtle, and causes a lot of head scratching during the learning process.

Consider: We've assigned a pointer to Twerp to a pointer to Dad. Now, can we access a field specific to Twerp through that pointer? No!

Try adding this to either Listing One or to Listing Two after the assignment of KidLink to Link.

Writeln(Link^.Girlfriends);

Now, Link points to a Son object, but the compiler will steadfastly claim ignorance that the Girlfriends field exists. (Turbo Pascal will give you error 44, and QuickPascal will give you error 60.) Hell, if we can access Twerp's methods through Link, we should be able to access Twerp's fields! And we can -- but only those fields that Twerp and Dad both have in common. Type Father defines an Age field and Son inherits it, but Son defines a Girlfriends field that Father doesn't have. A pointer defined as pointing to a Father type (as Link is) can't access things (either methods or data) that aren't defined in type Father.

Why not? Well, loosening assignment compatibility rules doesn't mean throwing them away entirely. Neither compiler is quite smart enough to deduce things across an assignment statement. Assigning a SonPtrto a FatherPtr doesn't change the fact that the FatherPtr is a FatherPtr in the compiler's symbol table. When you try to access a field called Girlfriends through a FatherPtr, the compiler checks the fields defined under type Father for a field called Girlfriends and doesn't see it.

If you want to access Son-specific fields through a FatherPtr you'll have to help the compiler a little, with some "Pizza Terra Typecasting." (See my May 1989 column for an explanation of that peculiar reference....) Try this in either Listing One or Listing Two, again, after the assignment of KidLink to Link:

Writeln (SonPtr(Link)^.Girlfriends.);

This time it works, both from a compile-time and a run-time perspective. Casting Link (a FatherPtr) onto a SonPtr lets the compiler check and see that, yes, there really is a Girlfriends' field associated with the pointer in question. All of the usual dangers of typecasting apply. As always, do what you must; just know what you're doing.

Typecasting used in this way kills the magic of polymorphism, in that what we're doing is peeking behind the Father mask to see the Son hiding there, and acting on that knowledge. Theoretically, we aren't supposed to know the type of the object "behind" a polymorphic assignment. However, if we want to make use of elements specific to the actual type of the object, peeking is our only recourse.

Products Mentioned

Programmer's Productivity Pack Falk Data Systems 5322 Rockwood Court El Paso, TX 79932 915-584-7670 $79.95

Mastering Turbo Pascal 5.5 by Tom Swan Howard W. Sams & Company, 1989 ISBN 0-672-48450-1 Softcover, 875 pages $25.95 Listings diskette $20.00

Repertoire PMI 4536 S.E. 50th Portland, OR 97206 503-777-8844 DOS: $149 OS/2: $189

Typecasting your way around polymorphic type-blindness can get you out of some tight corners on occasion. One way to avoid typecasting is to migrate fields and methods "up" the hierarchy so that all classes within a domain share all fields and methods, even if the fields are not used and the methods are empty in the more general classes. In our simple example, this would mean giving the Girlfriends' field to Father and letting Son inherit it, just as is done with the Age field. Better still, (and at the risk of sounding a little too Eastern) let the son be a son while he is a Son, but when he is playing Father let him leave his girlfriends at home....

Programmer's Productivity Pack

I'll never forget the rush of loading Sidekick for the first time back in 1984, and seeing a calculator that worked in hex. No more fiddling on paper and machine gun bashing on my ancient SR10. Sidekick changed the way I approached certain things in programming, because there was no longer any need to avoid working in pure hex or even binary.

The notion of memory resident tools took off with Sidekick, but mostly toward the users -- DOS shells to make picking files easier, things like that. It's nice to find an occasional TSR tool that won't do the users a damned bit of good. Such a one is Falk Data Systems' Programmer's Productivity Pack (PPP).

The PPP is a loose collection of useful gadgetry centering on a world-class programmer's calculator with four memories and a full set of bit-wise logical operators, including AND, OR, NOT, XOR, shifts, and rotates. The binary display field allows you to set individual bits rather than enter 31 digits just to set the MSB to 1. Values are displayed simultaneously in binary, hex, decimal, and (gakkh!) octal.

The calculator is good, but the most innovative gadget in the PPP is a sort of empirical keyboard reference screen that displays the scan codes of any key combination you can press on the keyboard. If you need to look up the scan code for a key combination, don't bother with a book; just press the key combination -- and you'll get the scan code. As a bonus, attached to certain key combinations are helpful notes indicating whether certain PC-family BIOS versions do or don't recognize a given key combination. Key code information is also given for the dBase data base and dBase compatible languages such as Clipper and QuickSilver, along with helpful notes such as pointing out that dBase and friends consider the Ctrl-\and F1 keys to be identical.

The shift and lock keys are also displayed as the bit patterns they really are. Beats thumbing through some tech ref manual any day. Other details are right on: Redefinition of PPP's several hotkey assignments, the ability to unload the utility from memory on command, an unusually readable manual, and a superb paper (!) ASCII code chart that I have framed on the wall beside the machine. There's more to it but I'm out of space at this point. Highly recommended.

Memory Hog Heaven

This is a good time to be a programmer. Tom Swan's newest book, Mastering Turbo Pascal 5.5 is on the streets; finally something good in print about objects. Eighty nanosecond, 1-Mbyte SIMMS are down to about $125 if you shop; by the time you read this they could be at $100. You can get a 20-MHz 386 system with 4 Mbytes of RAM for about $2500. With Windows/386 you can load Turbo Pascal, QuickPascal, PageMaker, and Paradox each into its own virtual-86 partition and carom from one to another like a rock off a canyon wall. Software development is more than just code; it's also data dictionary management and documentation. With Windows/386, you can build your own dream system for creating that ultimate app.

Good old days? We're here, guys. Really.

_STRUCTURED PROGRAMMING COLUMN_ by Jeff Duntemann [LISTING ONE]



PROGRAM PolyTest;  { For QuickPascal 1.0 }

         { BY Jeff Duntemann   }
         { For DDJ 1/90        }

USES Crt;

TYPE
  FatherPtr = ^Father;
  Father = OBJECT
        Age : Integer;
        PROCEDURE Init;
        PROCEDURE Talk;
      END;

  SonPtr = ^Son;
  Son    = OBJECT(Father)
        Girlfriends : Integer;
        PROCEDURE Init; OVERRIDE;
        PROCEDURE Talk; OVERRIDE;
      END;



VAR
  Dad     : Father;
  Twerp   : Son;
  Person  : Father;
  Link    : FatherPtr;
  KidLink : SonPtr;


PROCEDURE Father.Init;

BEGIN
  Self.Age := 42;
END;


PROCEDURE Father.Talk;

BEGIN
  Writeln('This is the old man talking.');
END;



PROCEDURE Son.Init;

BEGIN
  Self.Age := 11;
  Self.Girlfriends := 5;
END;


PROCEDURE Son.Talk;

BEGIN
  Writeln('Cool it, daddy-o!');
END;



BEGIN
  ClrScr;

  New(Dad);  { These three statements allocate the objects on the heap }
  New(Twerp);
  New(Person);

  Dad.Init;        { Type Father }
  Twerp.Init;      { Type Son }
  Person.Init;     { Type Father }

  { The following works polymorphically in QuickPascal but not Turbo: }

  Person := Twerp;   { Assign a Son object to a Father object }
  Person.Talk;        { Polymorphism allows the Son.Talk method to be }
           { executed from Person, which is type Father }
  Writeln('Person''s age is: ',Person.Age);  { Check the age on-screen! }
  Readln;

  { Working through pointers works with either Turbo or Quick Pascal: }

  Link := @Dad;      { Link is defined as a ^Father }
  Link^.Talk;        { Dad speaks here... }

  KidLink := @Twerp; { Twerp is type Son }
  Link := KidLink;   { Assign a SonPtr to a FatherPtr }
  Link^.Talk;        { Even tho Link is a FatherPtr, the kid talks here. }
  Readln;

END.





[LISTING TWO]


PROGRAM PolyTest;   { For Turbo Pascal 5.5 }

                    { BY Jeff Duntemann    }
                    { For DDJ 1/90         }


USES Crt;

TYPE
  FatherPtr = ^Father;
  Father = OBJECT
             Age : Integer;
             CONSTRUCTOR Init;
             PROCEDURE Talk; VIRTUAL;
           END;

  SonPtr = ^Son;
  Son    = OBJECT(Father)
             Girlfriends : Integer;
             CONSTRUCTOR Init;
             PROCEDURE Talk; VIRTUAL;
           END;



VAR
  Dad     : Father;
  Twerp   : Son;
  Person  : Father;
  Link    : FatherPtr;
  KidLink : SonPtr;


CONSTRUCTOR Father.Init;

BEGIN
  Age := 42;
END;


PROCEDURE Father.Talk;

BEGIN
  Writeln('This is the old man talking.');
END;



CONSTRUCTOR Son.Init;

BEGIN
  Age := 11;
  Girlfriends := 5;
END;


PROCEDURE Son.Talk;

BEGIN
  Writeln('Cool it, daddy-o!');
END;



BEGIN
  ClrScr;

  Dad.Init;        { Type Father }
  Twerp.Init;      { Type Son }
  Person.Init;     { Type Father }


  { The following does not work polymorphically in Turbo Pascal: }

  Person := Twerp;  { The assignment is kosher to the compiler...}
  Person.Talk;      { ...but polymorphism does NOT work here...  }
                    { ...and Dad speaks instead of the kid.      }
  Writeln('Person''s age is: ',Person.Age);
  Readln;

  { This, on the other hand, works polymorphically as it should: }

  Link := @Dad;       { Link is defined as a FatherPtr }
  Link^.Talk;         { Dad speaks here... }

  KidLink := @Twerp;  { Twerp is type Son }
  Link := KidLink;    { Assign a SonPtr to FatherPtr }
  Link^.Talk;         { Even tho Link is a FatherPtr, the kid talks here. }
  Readln;

END.