Objects
| sections in this chapter: |
|---|
| inheritance » |
| overriding » |
| recursion » |
| class-like-objects » |
Besides adjusting and expanding existing objects, you can also create new objects yourself simply by sending the message new. Suppose you would like to create an invoice object that enforces a numbering sequence, in that case, you will first need an invoice object.
Knowing that each object derives from another object, which, in turn, eventually originates from the root object of all objects called Object, a choice needs to be made regarding on which of those other objects your new object will be founded. Your new object inherits all properties of the object on which it is founded, so, to which the new message initially was sent.
In this example a rather neutral object is preferred, one without too many inherited properties. This offers an easy choice, because in that case the new object can be based on the root object itself which is Object. The notation of the intended invoice system will then be as follows:
Example:
>> invoice := Object new.
invoice on: ['start'] do: {
own number := 0.
}.
invoice on: ['number'] do: {
own number add: 1.
<- own number copy.
}.
invoice start.
Out write: invoice number, stop.
Out write: invoice number, stop.
Result:
The current invoice number is stored in the object, this is why there is a own keyword placed in front of it. This is called a property; more details will follow.
Some businesses prefer to have the year incorporated into the invoice numbers. In this case, you can create a new invoice object founded on a former invoice object, but which offers the possibility to its user to enter a specified year:
Example:
>> invoice := Object new.
invoice on: ['start'] do: {
own number := 0.
}.
invoice on: ['number'] do: {
own number add: 1.
<- own number copy.
}.
>> year-invoice := invoice new.
year-invoice on: ['start:'] do: { :year
own number := year.
}.
year-invoice start: 202000.
Out write: year-invoice number, stop.
Out write: year-invoice number, stop.
Result:
There is no need to write the implementation for the message number again, as it is inherited from the previously written invoice object; consequently, old code can be reused. Xoscript lacks concepts such as classes and other related concepts. This means that objects can solely inherit from other objects, also known as prototypical inheritance.
Now, to get back to properties. Object properties can only be approached from within the object hierarchy, so these properties cannot be perceived by other objects.
Contrary to other programming languages, all properties of objects are exclusively visible to the object that has created the property and to derived objects.
A commonly used technique is to use an object as a kind of blueprint, which is, for instance, the case with the invoices discussed earlier. Another good example is the Point. Let\’s say that you create a computer program that makes calculations by using points on a map. You can develop the object Point containing the properties of an x-coordinate and a y-coordinate, which will be made available to the external world through messages:
Example:
>> Point := Object new.
Point on: ['x-coordinate:'] do: { :x own x := x. }.
Point on: ['y-coordinate:'] do: { :y own y := y. }.
Point on: ['x-coordinate'] do: { <- own x copy. }.
Point on: ['y-coordinate'] do: { <- own y copy. }.
Point on: ['='] do: { :other
<- ( own x = other x-coordinate
and: own y = other y-coordinate ).
}.
>> pier := Point new x-coordinate: 5, y-coordinate: 6.
>> town-hall := Point new x-coordinate: 7, y-coordinate: 1.
>> restaurant := Point new x-coordinate: 5, y-coordinate: 6.
Out write: pier = town-hall, stop.
Out write: pier = restaurant, stop.
Result:
The = message compares the two points using the message and:. The first comparison results in a True or a False. These are boolean objects. When you send the message and: to a True object, and the argument object after de colon is equally True, then you will receive as answer again True. When either one is False, you will receive the answer False. Note that one point cannot read the coordinates of the other one. It is necessary to send a message to the other point to ask for this data: x-coordinate and y-coordinate. After all, as has been explained already, properties are solely retrievable from within the object family itself.
Note that the message = is used here to execute the point comparison. Equally, a different message could have been used, such as: is: or equal:, however reusing the = character seemed appropriate in this case.
Inheritance
You can reuse previously written code to create a hierarchy of objects. For example:
>> Animal := Object new.
>> Dog := Animal new.
>> Poodle := Dog new.
In this case, Dog inherits all the behavior and properties of Animal and, in turn, the object Animal inherits all behavior of Object, the root object of all objects.
Overriding
In the following example a new kind of list is created: Combination, in which each element is ensured to be unique. The functions of the Sequence are reused through inheritance:
Example:
>> Combination := List new.
Combination on: ['append:'] do: { :el
self
find: el, None? true: { self append: el. }.
}.
>> colours := Combination new.
colours
append: ['red'],
append: ['green'],
append: ['blue'],
append: ['red'].
Out write: colours, stop.
Result:
Observe how the second red is excluded from the sequence.
Occasionally, it may be necessary to override a behaviour of an object. For instance, when adding numbers and units of measurements have to be taken into account. In the following example the object Size is created. This object returns a number which, during the addition, takes into account the unit of the number to be added. The Number object checks whether it is about inches or foot. The programming code could resemble the following:
Example:
>> Size := Object new.
Size on: ['is:'] do: { :number
number on: ['+'] do: { :number
>> unit := number qualifier.
>> factor := 1.
unit
case: ['inch'] do: { factor := 0.0833. }.
<- self + (number * factor).
}.
<- number.
}.
# This size number can be used as follows:
>> plank := Size is: 6 foot.
>> edge := Size is: 50 inch.
# This will show as: 10.165
Out write: plank + edge, stop.
Result:
Recursion
In the previous example, the plus sign is being overridden. Note that eventually the final addition still has to be done, which, in fact, takes place on the following line:
>> answer := self + (number x factor).
Now, how does Xoscript understand that this plus sign refers to the original logic of addition?** For instance, another interpretation could be that Xoscript will repeatedly send the same message to the same object which, in turn, would result into an endless loop. Clearly, this is not the intention. As soon as you send a message to an object, which would execute the exact **same code, Xoscript will realise, in this case, that the underlying, overridden message is meant. So, your program will automatically be safeguarded against this form of endless loops. However, when it is indeed your aim to run the same function from the current function, it will be necessary to first send the message recursive. In this case, the result will be an infinite loop. However there are useful applications for recursive tasks, for instance, suppose that you want to calculate the factorial of a given number. In that case, simply add the message factorial to Number:
Example:
Number on: ['factorial'] do: {
>> answer := 1.
self > 0 true: {
>> previous := self.
>> next := previous - 1.
answer := previous * next recursive factorial.
}.
<- answer.
}.
Out write: 6 factorial.
Result:
This calls for recursion. In fact, the task that is connected to the message factorial, needs to be executed again within that function. So, it is necessary to invoke the factorial function from within the factorial function itself. As a rule, Xoscript will prevent this routine, because of the risk of ending up in an infinite loop. For this reason, it is vital to precede the message by the word recursive. This is done in order to make Xoscript aware of the fact that it is your intention to execute the same task, and that you did not make mistake.
Class-like Objects
Although xoscript does not offer classes, mimicking class-like behavior is trivial. Simply override the new message.
>> Animal := Object new.
Animal on: ['new'] do: {
<- self new init.
}.
Animal on: ['init'] do: {
own sound := ['?'].
own legs := 4.
}.
In the code fragment above, we create an object called Animal. This object serves as a blueprint for all kinds of animals like dogs and cats. The parent object Animal is written with a capital A to signify that this is a class-like object. Next we override the new message by writing our own new-method. In this case it sends a message new to self, followed by the message init. Because xoscript blocks recursion by default this won’t cause an infinite loop. Instead, it will send the new message to the parent object (Object) resulting in a new instance of Animal. So we create a new object, based on the same object we created our new-method for. This is perfectly valid. Now we also send the message init, this ensures that the Animal object has default values for its properties. By default an animal has 4 legs and the sound is unknown (hence the question mark). To create a new instance of our Animal, we could simply say:
>> a := Animal new.
Now we get a new instance of Animal with 4 legs, unknown sound and an init method. Just like in other OOP languages we can also extend our Animal:
>> Dog := Animal new.
Dog on: ['init'] do: {
self init.
own sound := ['barks'].
}.
We can test this easily by adding a string method:
Animal on: ['string'] do: {
<- own legs string + own sound.
}.
Let’s put it all together:
Example:
>> Animal := Object new.
Animal on: ['new'] do: {
<- self new init.
}.
Animal on: ['init'] do: {
own sound := ['?'].
own legs := 4.
}.
>> Dog := Animal new.
Dog on: ['init'] do: {
self init.
own sound := ['barks'].
}.
Animal on: ['string'] do: {
<- own legs string + own sound.
}.
>> poodle := Dog new.
Out write: poodle, stop.
Result:
Creating an object that is set in a given initial state, presents a frequent issue. Suppose an object Rectangle has to be created, in order to calculate perimeter and area. A possible notation would be:
>> Rectangle := Object new.
Rectangle on: ['area'] do: {
<- own length * own width.
}.
Obviously, setting a length and width is a precondition. For that purpose, the messages length: and width: can be added:
Rectangle on: ['length:'] do: { :length
own length := length.
}.
Rectangle on: ['width:'] do: { :width
own width := width.
}.
This rectangle can then be utilised as follows:
>> rectangle := Rectangle new length: 2, width: 3.
Out write: rectangle area.
However, if the initial settings of length and width are overlooked, an error message will occur:
>> rectangle := Rectangle new.
Out write: rectangle area.
will produce the following error:
Key not found: length
In order to prevent this, it is preferable that a rectangle should always have an initial length and width, for example 0. So, in this case, it is necessary to override the message new.
Example:
>> Rectangle := Object new.
Rectangle on: ['area'] do: {
<- own length * own width.
}.
Rectangle on: ['length:'] do: { :length
own length := length.
}.
Rectangle on: ['width:'] do: { :width
own width := width.
}.
Rectangle on: ['new'] do: {
# this will not result into an infinite loop
>> rectangle := self new.
rectangle length: 0, width: 0.
<- rectangle.
}.
>> a := Rectangle new.
Out write: a area, stop. #0
>> b := Rectangle new length: 2, width: 3.
Out write: b area, stop. #6
Result:
If we override the new-message we are basically mimicking a class.
The world of Xoscript is filled with a diversity of objects, which are available to your program right from the start. In the next chapters, all of these core objects will be discussed.