Pharo Objects Demystified: The subtle difference in how Messages work
Pharo is described as a “pure object-oriented programming language and a powerful environment” that has “objects and messages all the way down” according to the website. There are many questions one might have from this definition, but the main one for me was “How is a message send different from a function call in other Object Oriented Languages?” To answer this we must have some background.
Pharo is a dynamically typed language. It contains only objects, messages, and closures (blocks in Pharo parlance). It ultimately compiles down to C before it’s run. But it also allows for methods to be recompiled on the fly while the system is running. This creates a subtle but important difference when it comes to working with objects and messages.
Take the code below (Comments are in ““ in Pharo)…
| MyCounter |
MyCounter := Counter new.
MyCounter count. "-> returns 0"
(MyCounter count: 5) count "-> returns 5"
Counter
is a subclass of the basic Object
class in Pharo. To instantiate Counter,
you call new,
which automatically calls the initialize
method. New
, count
, and count:
are messages, with count
and count:
being getters and setters. Messages are late bound, so Pharo doesn’t know what message will be evaluated until it’s called. Messages also come in three types, unary, binary, and keyword whose operator precedence is evaluated in that order. Examples of each are…
3 factorial "-> Evaluates to 6 Unary"
2 + 2 "-> Evaluates to 4 binary"
(MyCounter count: 5) count "-> returns 5 Keyword"
The last example with MyCounter
is a special case. Messages in parenthesis will be evaluated first before all others. If not count
would be sent to 5
which would cause the error…
Instance of SmallInteger did not understand #count
Because you guessed it, 5
is an object as well. When an object is sent a message it does not understand, it goes up the hierarchy of inheritance. If it doesn’t find a message with the name you called, the keyword message messageNotUnderstood:
is sent to the original object with the parameter being the message it didn’t understand. It is up to the object to handle messages it doesn’t understand, though most just use the default behavior which is to raise an exception. But messageNotUnderstood:
is itself a message, so alternative behavior can be programmed.
Virtual Functions
And by the way. A Message is also an object which can be inspected in Pharo. Turtles all the way down! The last question you might have is “Well this just sounds like a virtual function in C++?” and you would be mostly correct. Virtual functions create a v-table for that function so that if the function is overridden at runtime the correct function can be called. The corollary to v-tables in Pharo are method dictionaries. They allow objects to look up the implementation of a message when it is sent to the object. The key difference here is that a method dictionary is mutable. So methods can be added or removed at runtime.
So in Summary…
Messages are handled at runtime and are late bound
Every object is responsible for what to do when a message is sent that it does not understand
Method dictionaries work like v-tables, but can be modified as runtime
Links
https://pharo.org/
https://pharo.org/features