Menu

Object Orientation

Object orientation - the truth

We already told you that C++ started its life as a mere preprocessor. In 4tH, all that is required to achieve almost full object orientation is a few humble preprocessor macro's. If you don't like object orientation, that's fine. You take the blue pill, the story ends, you wake up in your bed and believe whatever you want to believe. But if you take the red pill, you stay in Wonderland, and I show you how deep the rabbit hole really goes.

Object orientation comes with an abstract terminology that is incomprehensible to mere humans, but scratch away the shiny surface and you'll understand what it's all about. Stay with me.

Encapsulation

Object orientation defines encapsulation as ”a language construct that facilitates the bundling of data with the methods operating on that data”. So what are we actually talking about. Let's start with a structure that holds the data. Now add a few fields that hold execution tokens and you're done. That's your ”class” definition.

Building an object is nothing more than allocating storage space for your ”class”. You can reserve that space in static or dynamic memory. That's the easy part. The problem is, the fields for our execution tokens are still empty and when you ”instantiate” an object, you want these execution tokens to be initialized. The dirty trick is, you have to do that secretly - in true object oriented tradition.

In 4tH you define a class with a class definition. A class consists of three parts, the data definition, the ”binding” of the ”methods” and information hiding, e.g.

 :class Account
    extends Object                      \ data definition
      ffield:  accountBalance
      virtual: CheckBalance
    end-extends                         \ method binding

    :new 500 s>f this -> accountBalance f! ;method
    :virtual CheckBalance this -> accountBalance f@ ;method

    private{ accountBalance }           \ information hiding
 ;class

You can only use cells in the data definition. Sorry, you have to store your strings somewhere else. A data definition is almost identical to defining a structure, the only thing is you have to do it within the ”colon class” definition. Object orientation also requires a thing called ”open recursion”, which is nothing more than a ”this” or ”self” variable, which just holds the address of the ”object”. If you stay in the realm of the standard FOOS method declaration, this is done automatically for you.

The method defining words put the address (that is always on the top of stack when an object is invoked) on the return stack. Yes, you can still use the return stack, just think of the ”THIS” variable between two curly braces as the 'I' in a 'DO..LOOP' and you'll be fine. But there are a few pitfalls I might mention.

If you leave a method early by using 'EXIT' you may get in trouble. We will show you how to handle that later on. Basically, it is not much different from using 'UNLOOP' when exiting in the middle of a 'DO..LOOP'. And for obvious reasons 'DO..LOOP' doesn't play very well in conjuction with the ”THIS” variable.

The body of a ”:CLASS” definition is actually executed when an object is created. The constructor method, defined with the word ”:NEW” is part of those execution semantics, so the floating point ”property” (we call it a field) ”accountBalance” is set to 500. This is only some syntactic suger, though - so you can omit that construct if you prefer. Just don't leave the constructor early! You'll regret it if you try, since the actual constructor is the entire class definition itself.

The colon definition that starts with ”:VIRTUAL” is just a ':NONAME' definition, which leaves an execution token. ”;METHOD” on its turn pokes this execution token into the equivalent ”VIRTUAL:” field of the ”class”.

Note all classes you define are in some respect subclasses from the predefined mother of all objects, "Object". So if the superclass of a class isn't a class you defined, it's "Object". That's it. We're done, now let's use it.

 fvariable myBalance                    \ define an FP variable
 instance Account myAccount             \ create an Account object

The ”INSTANCE” word creates an object on the heap. Well, not just that; it also secretly calls ”Account”, so the object is properly initialized. It also passes the address of the newly created object to ”Account”, so it knows what to initialize. The curly braces in ”Account” make sure that ”THIS” works.

 myAccount => CheckBalance myBalance f! \ now pass a message
 myBalance f@ f. cr                     \ and store the result

You pass a message to an object by using the ”=>” operator. You can also use an ”adjective”. E.g. you can write the same piece of code like this if you want:

myAccount -> virtual CheckBalance myBalance f!
myBalance f@ f. cr

If you use '->' you'll just end up with the address of the methods field. When ”a message is passed”, a ”method is invoked”. ”Passing a message” means calling the object by name and passing all parameters. The object retrieves the reference of the actual method and executes it: that's the ”invocation of a method”. In 4tH, ”methods” can be ”virtual”, which means you can store another execution token in that field at any time.

When a ”method” is ”invoked”, 4tH secretly passes the address of the object, as a matter of fact right on top of the stack. That's why the method itself has curly braces as well - to store the address. Now it starts making sense:

  • The ”:NEW”, ”:DELETE”, ”:METHOD”, ”:VIRTUAL” or ”:DEFAULT word puts the object address on the return stack;
  • ”THIS” retrieves that address, so now ”accountBalance” points to the right place;
  • The ”;METHOD” word clears the return stack;
  • The data stack was uncluttered the whole time.

I told you folks, it's all smoke and mirrors. 4tH also supports static ”methods”, which means you can't change them later on and they're automatically passed to all subclasses. We could rewrite our previous example like this:

 :class Account
    extends Object                      \ data definition
      ffield:  accountBalance
      method: CheckBalance
    end-extends                         \ method binding

    :new 500 s>f this -> accountBalance f! ;method
    :method CheckBalance this -> accountBalance f@ ;method

    private{ accountBalance }           \ information hiding
 ;class

You cannot invoke static methods with the "=>" operator, you'll have to use "->" instead. Why? Well, a static ”method” is just an ordinary definition, so when you call it, the address of the object is consumed immediately. It is not expanded to an address to the ”method” field itself. Worse, static ”methods” do not even need a METHOD:” declaration, since they're never changed and consequently don't require any space. It's just a little syntactic sugar that does absolutely nothing.

Note that if you require to call a superclass method from a derived class that you can use a ":DEFAULT" definition. The difference with a ":VIRTUAL" definition is that we can never refer to the "original" method again once we reassign a virtual method.

With ":DEFAULT" you can. The reason is that 4tH doesn't have a virtual method table, although you can only do it once: multiple defaults for the same virtual method are not allowed. As a matter of fact, ”:DEFAULT” secretly defines a static method and assigns its execution token to the virtual method. So, the ”original” static method still has its own symbol in the symbol table, which you can later refer to using the ”<-” operator.

Subtype polymorphism

”Polymorphism” is the ability of ”objects” belonging to different types to respond to ”method” or ”property” calls of the same name, each one according to an appropriate type-specific behavior. Ok, that's a hard one to swallow. Let's break it down. We now know that is ”class” is nothing more than a structure.

In a previous section we already learned we could extend a structure. You may not realize it, but you can pass a pointer to an extended structure to a word that takes a pointer to the unextended structure and manipulate any of the fields they share - without problems. Of course, that includes fields that hold execution tokens. That is the basic principle of polymorphism.

Let's investigate that one a but further. Lots of animals create sounds. Let's make a ”class” for them:

 :class Animal
    extends Object
      virtual: Talk
    end-extends
 ;class

That's all. A ”class” with one ”method”, no ”properties” and no ”binding”. You'd probably call it a ”class with uninitialized methods”, but in object orientation it is usually called an ”interface”.

Ok, now we have an animal, let's define a dog:

 :class Dog
    extends Animal
    end-extends

    :virtual Talk ." Woof" cr ;method
 ;class

The ”EXTENDS” word allows you to indicate from which class this type is derived - in this case an ”Animal”. In this case, we don't add any ”properties” or ”methods”, but we do bind the previously defined method ”Talk”. We can do the same thing for a cat:

 :class Cat
    extends Animal
    end-extends

    :virtual Talk ." Meow" cr ;method
 ;class

If we now create a ”cat” object and a ”dog” object we can make them talk in their own peculiar way:

 static Cat MyCat
 static Dog MyDog

 MyCat => Talk
 MyDog => Talk

That's what's called ”dynamic dispatch”: when a method is invoked on an object, the object itself determines what code gets executed. You can take that a step further by defining e.g. a figure:

 :class figure                          \ define an empty class figure
    extends Object                      \ with no properties and two
      virtual: surface                  \ uninitialized methods
      virtual: outline
    end-extends
 ;class

Yes, that's an ”interface”. Now let's define a rectangle:

 :class rectangle                       \ define a subtype rectangle
    extends figure                      \ with two specific properties
      field: _width                     \ and a private method
      field: _height
      method: double
    end-extends
                                        \ now initialize surface and outline
    :method double 2* ;method
    :virtual surface this -> _width @ this -> _height @ * ;method
    :virtual outline
      this -> _width  @ this -> double
      this -> _height @ this -> double +
    ;method

    private{ double }                 \ make method private
 ;class

Note the method ”double” is private, which is means it can only be used within the class itself. That's why we made it a ”static” method, because it's not gonna be redefined any time soon. And let's also define a circle while we're at it:

 :class circle                          \ define a subtype circle
    extends figure                      \ with one specific property
      field: radius                     \ and a private method
      method: pi*
    end-extends
                                        \ now initialize surface and outline
    :method pi* 103993 33102 */ ;method
    :virtual surface this -> radius @ dup * this -> pi* ;method
    :virtual outline this -> radius @ 2*    this -> pi* ;method

     private{ pi* }                    \ make method private
 ;class

Both subclasses have very different properties, e.g. ”radius” has very little meaning in the context of a rectangle. However, they do share some methods, so if we instantiate them, we can ”ask” them to calculate their surface - or their outline:

 static rectangle MyRectangle           \ make a rectangle instance
 static circle    MyCircle              \ make a circle instance

 4 MyRectangle -> _width !              \ initialize the rectangle
 5 MyRectangle -> _height !
 MyRectangle => surface . cr            \ use both methods
 MyRectangle => outline . cr

 25 MyCircle -> radius !                \ initialize the circle
 MyCircle => surface . cr               \ use both methods
 MyCircle => outline . cr

Nothing to it - if you know what's under the hood.

Inheritance

The final feature true object orientation requires is inheritance. Inheritance is a way to reuse code, which means that classes can inherit attributes and behavior from pre-existing classes called ”superclasses”. So, how is this done in 4tH? Let's define a traffic light, a simple one:

 :class two-light                       \ create a two light traffic light
   extends Object
      field:  Red                       \ red color string
      field:  Green                     \ green color string
      field:  State                     \ state of the traffic light
      field:  #lights                   \ number of lights
      field:  Description               \ description of lights
      virtual: Switch                   \ change to the next state
      virtual: Show                     \ show the current state
    end-extends

Ok, this is our traffic light, the one you find on pedestrian crossings. It's got a red light and a green light - which totals two lights. It's got a state so we can see what light is on at any time and a few methods. ”Switch” makes it change from one state to another and ”Show” shows the traffic light.

Now let's initialize our object:

:new                                \ set the colors
   s" Red"   this -> Red   dup ds.init ds.place
   s" Green" this -> Green dup ds.init ds.place
                                    \ we need a table of descriptions
   here ['] Green , ['] Red , here this -> Configure
;method                             \ now configure it

Here we see a nice example of how we can make dynamic strings work for object orientation with very little trouble. We already concluded that it has two lights, so no surprises there and we configure it by calling the ”Configure” method. This method manipulates a pointer named ”Description” which points to an unnamed table which we'll use in the ”Show” method:

:method Show                           \ show the current color
     cells this -> Description @ + @c cells this + ds.count type cr
;method                                \ note we fix the description offset

On a ”method” level it just takes an integer, representing the ”State” of our traffic light. Next we fetch the item from the table, which is the offset to the appropriate ”property” of the ”object”. So we add this offset to the current object address and fetch its contents. Those contents point to a string in dynamic memory, which we can type. Phew! Job done. The rest is straight forward:

 :virtual Switch                        \ assign it to the Switch method
    this -> State dup @ dup             \ get the current state
    this -> Show 1+                     \ show it, go to next state
    this -> #lights @ mod swap !        \ and set it
 ;method

:delete this -> Green ds.free this -> Red ds.free ;method

The ”:DELETE” method is a destructor, which frees the memory taken by the ”lights” and finally its own, since that behavior is inherited from the mother of all classes ”Object”. The ”Switch” method not only reads property ”State”, it also updates it, so it completely encapsulates it. Nothing else, but this class alone, has any business to do with ”State”, so we can make it private. BTW, that also goes for ”#lights", ”Description” and ”Show”:

   private{ State #lights Description Show }

Now let's make a heavier duty traffic light:

 :class three-light                     \ create a three light traffic light
    extends two-light                   \ based on the two light traffic light
      field: Yellow                     \ add the color Yellow
    end-extends

We only need to add the yellow light, don't we? Sure, we need to add a string for the yellow light:

:new                                   \ set additional color
   s" Yellow" this -> Yellow dup ds.init ds.place

The configuration may be a bit different, so we have to make and configure a new one:

                                       \ we need a new table of descriptions
   here ['] Green , ['] Yellow , ['] Red , here this -> Configure
;method

But the rest is just about the same, is it? Not quite. If we destroy our traffic light, we don't want to leave a yellow light on the sidewalk:

:delete this -> Yellow ds.free ;method

Yes, we destroy our yellow light first, then the rest of the traffic light (including the traffic light itself) is destroyed by the superclass destructor, which is called automatically. A two-light traffic light won't behave the same as a three-light traffic light. But we only need to change the bits that are different. The rest is inherited. If it's a three-light traffic light, it will behave accordingly. You can see that when you actually use the classes:

 instance two-light DontWalk            \ define a pedestrian light

 ." A pedestrian traffic light:" cr cr

 DontWalk => Switch                     \ go to the next state four times
 DontWalk => Switch
 DontWalk => Switch
 DontWalk => Switch
 DontWalk delete cr                     \ now destroy it

 instance three-light TrafficLight      \ define a normal light

 ." A normal traffic light:" cr cr

 TrafficLight => Switch                 \ go to the next state four times
 TrafficLight => Switch
 TrafficLight => Switch
 TrafficLight => Switch
 TrafficLight delete cr                 \ now destroy it

Now you think: ”how does he do that?” Well, frankly: by cheating! If you expand the source, you will see what is actually going on:

['] two-light ['] three-light ['] ~three-light (~~init)

It passes three execution tokens to a secret helper word that invokes the ”superclass” initialization before invoking the initialization of the ”subclass”. If that superclass is a subclass itself, it will call that superclass initialization and so on. When a superclass has finished it relinquishes control to the subclass, so it can do its thing. Finally, the original subclass itself gets the chance to override any ”properties” or ”methods” it sees fit - and then we're done. Hurray, we got an object - initialized and all.

It's like you make a painting and paint over it a few times. What you see is the final picture. You're unaware of what is hidden under the various layers of paint - but that doesn't mean it's not there or it was never painted. It's that simple.

Lazy initialization

Lazy initialization is the tactic of delaying the creation of an object, the calculation of a value, or some other expensive process until the first time it is needed. It is a kind of lazy evaluation that refers specifically to the instantiation of objects or other resources. You'll find these a lot when using singleton or multiton design patterns.

In most OO languages, the name of the class itself serves as a placeholder for the object. Since that is the name of the actual constructor - and hence is executed - that is not an option in FOOS. Instead, we have ”NOTHING”.

You can use this only in conjunction with static methods - which is fairly obvious, since there is no way to invoke a virtual method without an actual object. Of course, you can't access any properties as well - they're not there. What do you want?

Note that since the value of ”NOTHING” is equal to '(ERROR)' you can create some pretty expressive code in combination with methods that use the latter as an error value:

:method getFruit
   2dup types -> hget Nothing =
   if 2dup new Fruit -rot types -> hput else 2drop then
;method

In this case, a new instance is only created, when its value is not in the hash map. Calling the static method above is done like this:

s" Apple" Nothing -> getFruit

Agreed, FOOS is a bit different, but with alternative constructs it allows you to implement many OO design patterns without sacrificing ease of use or clarity.

Determining the type and size of an object

You can determine the type of an object by simply fetching it, e.g.:

 MyCircle type@

You can compare it to a predetermined class by using the ”TYPEOF” word:

 typeof circle

You can also determine the superclass of an object:

 MyCircle parent@

And its counterpart on the class side is used like this:

 parentof circle

You can compare these values with the '=' word, nothing special. The value returned is actually an execution token of the class, so you can use it when you e.g. want to clone objects. It is secretly stored in a hidden field of the class. Of course, what's missing there is the size of a class. You can determine that one with the ”SIZEOF” word, e.g.

 sizeof circle

If you've allocated objects on the heap, getting their sizes isn't rocket science either:

 MyCircle allocated

For static objects there is no way to determine their sizes (in true 4tH fashion), but that will not be a problem, because you won't be allocating them in a user defined word.


Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.