Pharo Superpower: Send Any Message
In Pharo Smalltalk you may send any message even if it’s name is not known at compile time. Sending any message is one of the superpowers that can be used for the good, even when doing application programming, therefore I will discuss best practices in the end.
First of all, recall that “sending a message” is Smalltalk jargon for calling a method. Since sending a message is synchronous in Smalltalk, ie it blocks until the receiver returns, it is basically the same as a method call and the actual difference thus of philosophical nature only. (There is an implementation difference deep down at the language’s core, but that shall not be discussed today as it does not matter to programmers.)
BTW, this is the fourth post in the Superpower series.
There are many ways to send any message, mainly due to optional and variable arguments which are not well supported by Smalltalk syntax. The most basic form is
object perform: #symbol withArguments: anArray
Let’s consider a real example
string = 'Lorem'. string perform: #size. "=> 5" string perform: #at: with: 1. "=> $L" string perform: #copyFrom:to: with: 2 with: 4. "=> 'ore'"
If the number of arguments is not known at compile time, we may use
string perform: aSymbol withArguments: anArray. string perform: aSymbol withEnoughArguments: anArray.
the first expects that the array matches the arity (number of arguments) of the target method, the latter will just use as many arguments are required. This is most useful to send a message with optional arguments.
So when is sending any message for the good or for the evil?
Whenever possible, try to avoid using
#perform: because it is less readable. When a reader of your program looks at the a
#perform: it is not obvious which message is being sent at runtime. Also, messages that are sent with
#perform: will not be shown when browsing all senders of a message. There is one subtle difference here: If the dynamically sent message is stored somewhere as symbols, at least that symbols will show up when looking for senders. If however, the dynamically sent message is composed using string concatenation, it wont show up at all. It might even seem as if its implementers are never used, which can be very confusing to the reader.
For all above reasons you should only use
#perform: when you have good reason to do so. And if you use it, make sure that the dynamically sent messages are all stored as a symbol somewhere else. Best of all, make sure that all code involved into dynamically sending message is encapsulated by one single class.
I will provide an example from Hapax’s clustering algorithm. When you do hierarchical clustering, there are different ways to link small clusters into large clusters. The call to this linkage method is buried deep down in the internals of the clustering algorithm, so the
ClusteringEngine class uses a strategy pattern to pick the right linkage method. The choice of strategy is stored as a symbol in an instance variable and then used as follows
Object subclass: #ClusteringEngine instanceVariableNames: 'distanceMatrix dendrogram linkage' classVariableNames: '' ClusteringEngine >> linkage: aSelector linkage := aSelector ClusteringEngine >> linkage ^ linkage ClusteringEngine >> allLinkageSelectors ^ #( averageLinkage centroid completeLinkage meanLinkage singleLinkeage wardsMethod ) ClusteringEngine >> run (distanceMatrix size - 1) timesRepeat: [self findMinimum. self perform: linkage]. ClusteringEngine >> averageLinkage "implementation omitted..." "et cetera..."
All code is encapsulated with one class such that a reader can find it all in one place when browsing the source code.
Any other use of
#perform:, in particular when string concatenation of selectors is involved, is evil and should be limited to library design, if used at all.
A note regarding performance. Using
#perform: is as fast as sending a message the normal way. So contrary to popular believe there is not performance penalty—at least not in Pharo Smalltalk, in other dialects that do use JIT compilation there might be severe performance penalties though).