Prothon Tutorial

 Prothon Home Previous Page Tutorial Outline Next Page

6.0 Prototype-based Programming

6.1 Making Your Own Object

So far you have created many objects using special creation methods built into the Prothon language such as integer and string and list constants. It's time to learn how to build objects from scratch. The most fundamental way to build an object is to use the form "obj = Object()". This creates an "empty" object. It has no binary data and no attributes. Let's create an empty object called "Rectangle":

>>> Rectangle = Object()
<Object:939f40>
>>>

Prothon displays the empty object by just showing it's address in memory in hexadecimal. This address is sometimes known as the object's ID. This can be useful at times because the address of an object never changes for the life of the object. When you compare two objects using the "a is b" operator, Prothon just compares the addresses.

If we want the new Rectangle object to represent a real rectangle, we should give it a width and height. We can use attributes to do that. Let's also add a method (function that uses "self", also in an attribute) that calculates it's area:

>>> Rectangle.width = 10
10
>>> Rectangle.height = 10
10
>>> def Rectangle.area():
...    return self.width * self.height
...
>>>

We promised you earlier that the with statement would be useful. Here is our chance to use it. Let's redo that last example using the "with" statement. Let's also show the area method in action:

>>> with Rectangle:
...    width = 10
...    height = 10
...    def area():
...       return self.width * self.height
...
>>> print Rectangle.area()
100
>>>

So now we have an object called "Rectangle" that represents a Rectangle by storing its width and height in attributes called "width" and "height" and can calculate its own area by it's "area" method that is also stored in an attribute called "area".

Those of you familiar with object-oriented programming might notice that this is very similar behaviour to an object instance from a class called "Rectangle", yet we have not defined any class at all, we just created the object directly. This is much simpler and more direct. This is called "object-centric" programming instead of "object-oriented" programming even though it is obviously "oriented" towards objects. It is just that all the books and courses have already been written that say that object-oriented programming has classes and it's a bit too late too argue with them.

At this point we might want a lot of Rectangles. We can always make a second one by copying it. This is how some languages, and the Self language in particular, makes more of a "type" of object:

>>> rect2 = Rectangle.copy()
>>> print rect2.area()
100
>>> rect2.width = 50
50
>>> print rect2.area()
500
>>> print Rectangle.area()
100
>>>

So you can see that we now have two objects each with different width attributes and different areas that represent different rectangles. At this point there is nothing stopping us from using a for loop to make a thousand rectangles.

So we can make many instances of rectangles that are just like the first Rectangle. That is why we call the first one the prototype. It is not like the class in an object-oriented language. A class is just a blueprint for making objects. A prototype is a real, working instance of an object. You can make the prototype and test it before you use it to make other instances. Using prototypes instead of classes is what makes Prothon a prototype-based language instead of a class-based language.

So did I trick you and let you think you were just making an object when you were really making a prototype? No, in reality every object in Prothon can also be a prototype. You could copy "rect2" to make a "rect3" and use it just like you copied "Rectangle". Later you will find out things that make certain objects better at being prototypes than other objects, but Prothon is an equal opportunity employer. Any object may apply to be a Prototype.

6.2 Parent Prototype Links

You may have noticed that the instances created above by the copy commands above have some disadvantages compared to object-oriented intances made by classes. The biggest problem is storage efficiency. Each one of the rectangle instance objects has its very own copy of the area method. Also, what if these objects are stored away somewhere and we decide we want to make an improvement to the Prothon code for the area method? It would be nice to change the code in one place and have it change for all rectangles. It would not be fun to change the code in a thousand different area methods.

To solve that problem and to add many new features, Prothon has added a third feature to objects, after binary data and attributes. This is an ordered list of links to prototypes. Each object has at least one reference (link) to an object that is its "parent prototype". There can be more than one but let's discuss the case of one for right now.

The parent prototype is just an ordinary object. The parent prototype is used whenever the object is having an attribute looked up and the attribute is not found. In this case, the search for that attribute continues in the parent's attributes. If the attribute isn't found there, then it continues in the parent's parent (grandparent's) attributes. In other words, search for attributes goes up the chain of prototype objects linked together by the prototype links.

You might wonder what ends this prototype chain. There is a special object, cleverly named "Object", that is the end of all prototype chains. It is the ultimate parent of all objects. The common way to create an empty object is to give it one prototype link to Object. That is what we did earlier when we said "Rectangle = Object()". (We will explain this statement in the next paragraph). To show this, will use the function protoList() which returns the prototype chain for an object as a list. It includes Rectangle as the head of the list and Object as the end:

>>> print Rectangle.protoList()
[<Object:93b100>, <Object:923020:Object: prototype base for all objects>]
>>>

So what good is attribute lookup in prototypes? Let's redo rect2 using a prototype link instead of doing a copy. The easiest way to create an object that has a prototype link to another object is to use the form "obj = Prototype()". This looks exactly like a function call, but we know it isn't, because we know "Prototype" is not a function object. This will create a new object that has no binary data or attributes but it will have one prototype link to the object "Prototype". Now you can see what our original statement "Rectangle = Object()" was doing. It created an empty object with only a prototype link to Object.

>>> rect2 = Rectangle()
>>> print rect2.attrs()  # attrs() returns attrs as dictionary
{}
>>> print rect2.width, rect2.height, rect2.area()
10 10 100
>>>

We created a new object called rect2 with no attributes as before, but this time we gave it a prototype link to Rectangle instead of copying it from Rectangle. We showed that it had no attributes of it's own by printing them out using the .attrs() function. Yet when we accessed width, height, and area we got the same results as before. This is because each of the attribute lookups, both for data and functions, failed on rect2 so the lookup went to Rectangle where the lookup succeeded.

In the case of rect2.area() resolving to Rectangle.area(), this kind of method lookup reminds us a lot of the method inheritance in object-oriented programming. So we sometimes say in Prothon that rect2 "inherited" the method area from Rectangle. In Prothon, we also "inherit" data since we lookup all attributes in the prototype parents. This is not done in object-oriented languages and it is a powerful advantage of prototype-based languages. Let's continue the example:

>>> rect2 = Rectangle()
>>> rect2.width = 33
>>> rect2.height = 12
>>> print rect2.attrs()
{'width':33, 'height':12}
>>> print rect2.area()
396
>>>

Now we have a rectangle object with it's own width and height attributes only, and it behaves as if it had an area method, because it has the prototype "Rectangle". Also, if we were to change the code of the method "area" in the "Rectangle" prototype, then the behaviour would change in rect2 also.

You can see that we could make a thousand objects that each have a link to Rectangle and each object would only need to have the data that is unique to that object. Also, any of the objects could have a different area method for different behaviour if you wished. Prototype-based programming is very flexible compared to class-based programming in addition to being simpler.

6.3 Object Statement

There is a convenient statement, the "Object" statement, that combines the creation of the object and the with statement. The following replaces the first three code examples in this section 6:

>>> object Rectangle:
...    width = 10
...    height = 10
...    def area():
...       return self.width * self*height
...
>>>

The general form of the Object statement is "object name(prototypes): body". "Name" is the new object being created. "Prototypes" is the list of prototypes for the object. If the list is missing, as in this example, then just Object is assumed. "body" is the exact same body that is in the "with" statement that is executed exactly once immediately.

 

 Prothon Home Previous Page Tutorial Outline Next Page