What's so special about message passing in Smalltalk?
Asked Answered
I

5

24

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.

Interne answered 28/2, 2017 at 0:45 Comment(1)
Don't confuse keywords with messages in Smalltalk. Smalltalk has a tiny number of reserved keywords (depending on the dialect, typically 5 or 6), everything else is messages being sent from object to object in a library you can modify. Even operators like + are messages sent to number instances (not primitives, but objects) with the second number as the argument. Smalltalk's syntax is simply, "object message" with the result always being an object (so you can send another message to the result, etc.). Some symbols have special meaning; see wiki.c2.com/?SmalltalkSyntaxInaPostcard :-)Alastair
M
29

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.

Myra answered 28/2, 2017 at 23:21 Comment(6)
So the basic difference is that Smalltalk is dynamically typed (detects type errors at runtime, allowing to handle exceptions) while C++ is statically typed (detects type errors at compilation time)?Bruise
No. The basic difference is that a Message is a reified concept in Smalltalk, whereas in C++ the virtual function dispatch is implicit. In Smalltalk you can implement an object that will accept any message with any kinds of arguments (as mentioned in the last paragraph of my answer). There is no way to do that in C++. Error handling is just one of the applications, writing generic proxies for remote procedure calls is another, instrumenting your code for runtime analysis is another, etc.Myra
@Myra is right, and it is actually the best way to think of things in Smalltalk - the entire Smalltalk vocabulary is set up for it - you "send" a message to its "receiver" - the message is a thing unto itself - there's a Message class you can look at! - the debugger shows you actual message objects, you can get one and save it away to do something with it later, you can forward the Message - whatever it is, whether you understand it or not - to some other object as-is. (Pay no attention to the man behind the curtain: How it is implemented, on a stack, is none of your concern ...)Ouster
What does this mean in practice?Undertaker
@user16217248 I'll use Ruby instead of Smalltalk for an example. It's much the same concept, but a more approachable language IMO. In Ruby you can make a mock object which can mock any arbitrary method call (expecting particular values, returning some stubbed result, etc.). This is possible without needing to refactor all your real code to have an IWidget interface with a RealWidget: IWidget class and a test double FakeWidget. Take a look at some of the examples here: github.com/freerange/mocha#quick-start This is only possible because the system gives you a way to...Tasman
@user16217248 intercept message passing. You can have a class implement method_missing, which is a catch-all for any message passed to it that hasn't yet been defined. This unlocks huge meta-programming possibilities. Mocking is one, but also distributed objects, XML builder DSLs, etc. The latter example is interesting: you can write xml.foo to get the xml builder object to produce a foo tag, without there being a foo method defined on it.Tasman
S
16

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.

Sawfish answered 28/2, 2017 at 6:15 Comment(7)
@JayK I would "promote" this comment to an answer. Would you do that please?Utopianism
I think there is a subtle difference beyond the implementation mechanics: when you think in terms of message passing, you don't think in terms of 'global context ownership'. When you call a function, that function now 'owns the world'. Sending a message feels different; you don't expect the object receiving the message to 'take over'. It's similar to the difference between a program manipulating data structures; conceptually, any function can manipulate any data. This mindset influences design. I think for the better.Ulcerate
@LeandroCaniglia did so. Now it might be too long for some readers, though. ;-)Clie
@BobNemec Very good point. See also Object Oriented Programming by Tim Rentsch (1982); especially the third paragraph under Messages pg. 54.Utopianism
"...*during the compilation time you know which function will be called*...": I fthat were true, C++ wouldn't allow polymorphism (dynamic binding), making it rather useless as OO language.Bruise
@U.Windl you are right. And it's fun to read my 5 year old answers. On the other hand, there are still certain guards that statically typed languages employ to ensure, that an object will be able to execute a requested method. This gives you some confidence and allows to use the "call" terminology, which is the difference with messagingSawfish
Your old answer is not wrong at all @Uko. Polymorphism is only one of the things that message passing can be used for. But in C++, polymorphic member function calls is the only thing supported. There is no way to write a generic virtual function in C++ that can be called under any name and with any arguments. Which is precisely what true message passing allows.Myra
C
14

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.

Clie answered 1/3, 2017 at 0:22 Comment(0)
O
8

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.

Outgo answered 28/2, 2017 at 7:53 Comment(1)
"It's messages all the way down" - yes, but not objects all the way down. For that you want Self.Ouster
A
2

"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.

Autotype answered 15/5 at 23:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.