Agnivesh Singh
Agnivesh Singh

Reputation: 537

What's so special about message passing in Smalltalk?

I was going through an introduction to Smalltalk. In C++, the functions declared inside a class can be called by objects of that class, and similarly in Smalltalk a keyword, termed as message, is written adjacent to the name of the object (don't know much but would also like to ask here whether in response to a message a unique method is there to be executed?)

Basically, to my naive mind, this seems to be only a difference in syntax style. But, I wonder if internally in terms of compilation or memory structure this difference in calling holds any significance.

Upvotes: 24

Views: 8143

Answers (5)

Jim Sawyer
Jim Sawyer

Reputation: 143

"this seems to be only a difference in syntax style" is a common mistake.

The essential difference is found in the definition of "object oriented". The language is "oriented" toward objects in that communication occurs between objects by way of messages sent between them.

And what is an object? Everything.

What does this mean?

It means that everything that can receive a message is an object, everything that can send a message is an object, and everything that can be sent as a parameter is an object. So when a method is dispatched, the parameters are always objects.

In implementation terms, an object is either a literal value or a reference to an instance of a class. Which means the parameter, as a single word on the stack, tells the receiver everything it needs to know.

In C++, or Java, or many other "Object" languages, the word on the stack is also just some bits--it might be a primitive value, or it might be an address pointing at memory, but you cannot tell by looking at it. It is not an object, it is unknown bits. One must tell the compiler what kind of thing is on the stack (a primitive or a reference) and if it is a reference, one must also tell the compiler what 'type' defines the layout of the thing the reference is pointing to. Whereas if the parameter was known to be an object, and the language was oriented toward objects, the bits would tell you what 'the object' is, and if it is a reference, the object itself would know what 'type' it is, so it would be sufficient to send just 'the object' to the receiver.

This has consequences.

For example, when you put an actual object into a collection and take it back out, you do not have to know what 'type' of variable you are storing it into. That information is known to the object, not the variable.

'Object' languages must communicate both the address and the type every time they 'send a message'. Object-oriented languages only need to communicate in terms of objects, because the objects know their own type. When the receiver needs to know the type of object it received as a parameter, it can ask the received object--by sending a message to it.

There is no reason to tie oneself to the drudgery of redundantly specifying the type at both ends of every interaction if the objects already know this. An object can be sent from one place to another and to another and to another and only the final destination actually needs to know what 'type' it is.

Think about that. All the infrastructure that is just routing things from place to place can be shared. You can change your mind as to what 'type' you will use to implement an entity, and most of the code you've already written does not have to change. The entity still gets passed to the bottom of the call chain, and a result is still returned. So maybe you change the method at the top and bottom to work with the new type--but nothing else has to change. One can often formulate an entire solution, and write 90% of the code, and test the majority of it, before finally deciding on the 'type' of the main object.

So no. It is not just a difference in syntax.

Upvotes: 2

JayK
JayK

Reputation: 3141

There are already good answers here. Let me add some details (originally, part of this was in a comment).

In plain C, the target of each function call is determined at link time (except when you use function pointers). C++ adds virtual functions, for which the actual function that will be invoked by a call is determined at runtime (dynamic dispatch, late binding). Function pointers allow for custom dispatch mechanisms to some degree, but you have to program it yourself.

In Smalltalk, all message sends are dynamically dispatched. In C++ terms this roughly means: All member functions are virtual, and there are no standalone functions (there is always a receiver). Therefore, the Smalltalk compiler never* decides which method will be invoked by a message send. Instead, the invoked method is determined at runtime by the Virtual Machine that implements Smalltalk.

One way to implement virtual function dispatching is virtual function tables. An approximate equivalent in Smalltalk are method dictionaries. However, these dictionaries are mutable, unlike typical virtual function tables, which are generated by the C++ compiler and do not change at runtime. All Smalltalk behaviors (Behavior being a superclass of Class) have such a method dictionary. As @aka.nice pointed out in his answer, the method dictionaries can be queried. But methods can also be added (or removed) while the Smalltalk system runs. When the Smalltalk VM dispatches a message send, it searches the method dictionaries of the receiver's superclass chain for the correct method. There are usually caches in place to avoid the recurring cost of that lookup.

Also note that message passing is the only way for objects to communicate in Smalltalk. Two objects cannot access each other's instance variables, even if they belong to the same class. In C++, you can write code that breaks this encapsulation. Hence, message sending is fundamental in Smalltalk, whereas in C++ it is basically an optional feature.

In C++, Java, and similar languages, there is another form of dispatch, called function overloading. It happens exclusively at compile time and selects a function based on the declared types of the arguments at the call site. You cannot influence it at runtime. Smalltalk obviously does not provide this form of dispatch because it does not have static typing of variables. It can be realized nevertheless using idioms such as double dispatch. Other languages, such as Common Lisp's CLOS or Groovy, provide the even more general multiple dispatch, which means that a method will be selected based on both the receiver's type and the runtime types of all the arguments.

* Some special messages such as ifTrue: ifFalse: whileTrue: are usually compiled directly to conditional branches and jumps in the bytecode, instead of message sends. But in most cases it does not influence the semantics.

Upvotes: 14

codefrau
codefrau

Reputation: 4623

The fundamental difference is that in Smalltalk, the receiver of the message has complete control over how that message is handled. It's a true object, not a data structure with functions that operate on it.

That means that in Smalltalk you can send any message to any object. The compiler places no restrictions on that, it's all handled at runtime. In C++, you can only invoke functions that the compiler knows about.

Also, Smalltalk messages are simply symbols (unique character strings), not a function address in memory as in C++. That means it's easy to send messages interactively, or over a network connection. There is a perform: method that lets you send a message given its string name.

An object even receives messages it does not implement. The Virtual Machine detects that case and creates a Message object, and then sends the messageNotUnderstood: message. Again, it's the object's sole responsibility of how to handle that unknown message. Most objects simply inherit the default implementation which raises an error, but an object can also handle it itself. It could, for example, forward those messages to a remote object, or log them to a file, etc.

Upvotes: 29

aka.nice
aka.nice

Reputation: 9382

Here are a few example of what you would not find in C++

In Smalltalk, you create a new class by sending a message (either to the superclass, or to the namespace depending on the dialect).

In Smalltalk, you compile a new method by sending a message to a Compiler.

In Smalltalk, a Debugger is opened in response to an unhandled exception by sending a message. All the exception handling is implemented in term of sending messages.

In Smalltalk you can query the methods of a Class, or gather all its instances by sending messages.

More trivially, all control structures (branch, loops, ...) are performed by sending messages.

It's messages all the way down.

Upvotes: 8

Uko
Uko

Reputation: 13386

You call a function in C++ because during the compilation time you know which function will be called (or at least you have a finite set of functions defined in a class hierarchy.

Smalltalk is dynamically typed and late bound, so during the compilation time you have no idea which method is going to be evaluated (if one will be at all). Thus you send a message, and if the object has a method with that selector, it is evaluated. Otherwise, the "message not understood" exception is raised.

Upvotes: 16

Related Questions