Saturday, September 10, 2005

High Order Messaging

Marcel Weiher sent a mail on the objc mailing-list about a paper he wrote (with Stéphane Ducasse) on High Order Messaging. Basically, HOM is a message that takes another message as a parameter -- like in functional language you have High Order Functions that take a function as parameter.

Ok, nice name .. but why is it interesting ? Well, you have lots of cases where it's really neat :-) but the most known is to use it for iterations. Imagine you have an array containing objects, and you want to send to all of them a message. In Objective-C you'd do:

int i;
for (i=0; i<[myArray count]; i++)
id currentElem = [myArray objectAtIndex: i];
[currentElem aMessage];

You could also use an enumerator (NSEnumerator) but that doesn't really simplify things. What's the problem with that ? Well, you have code (the for loop..) that you don't really care about, that is actually always the same, you need to deal with each elements, etc. Basically, the programmer's intent is not immediately clear. And of course, the more code you have, the more you can encounteer bugs... So simplifying this pattern would be great. Well, with HOM you will do:

[[myArray do] aMessage];

Better, no ?

Conceptually, the message "do" takes another message as parameter -- it's a HOM. Here it actually bounces the message to all the elements of the array -- therefore, encapsulating the iteration pattern.

Technically, it's a rather cool usage of the Objective-C runtime -- no need to change anything to your compiler :-) -- the HOM message actually returns a Trampoline object that will get the message send to it via forwardInvocation. Read the paper for a good description.

So, using HOM is great for encapsulating the iteration pattern, ok. But after all, we have the makeObjectsPerformSelector.. so why using HOM ?.. Well, the difference is that YOU can create new HOMs, and you are not limited to the iteration pattern :-)

Another good example from the paper.. You have a delegate object you need to send a message to. You'll have the following code:

if ( [delegate respondsToSelector: @selector(windowDidClose:) ]
[delegate windowDidClose: self];

Again, this is a frequent pattern. Moreover, it's prone to errors -- the selector in the test needs to be equal to the message you send.. An HOM equivalent will be:

[[delegate ifResponds] windowDidClose: self];

Elegant, no ?

Just to finish.. some other examples from the paper:

Starting a method in a thread is done like that in OpenStep:

[NSThread detachNewThreadSelector: @selector(doSomething)
toTarget: receiver withObject: nil];

The HOM equivalent:

[[object async] doSomething];

Or say you want to send a message with a delay.. In OpenStep:

[receiver performSelector: @selector(doSomething)
withObject: nil afterDelay: 1.0];

With HOM:

[[object afterDelay: 1.0] doSomething];

I remember reading about HOM on Marcel's site before this paper, but this paper is an excellent presentation of the idea and do a great job highlighting the possibilities -- another useful mechanism to add to your toolbox to capture patterns :-)

Many HOM implementations exists (in addition to Marcel's original, MPWFoundation), that you can check on that page


Anonymous said...

At first I didn't really get the difference with doing things like:
[myArray eachPerform: @selector(aMessage)];
[object asynchronouslySend: @selector(doSomething)];
[object afterDelay: 1.0 send: @selector(doSomething)];

When I see [[myArray do] aMessage] I wonder what's returned by do and sent aMessage... When you give instructions to someone, you actually give him instructions, that he will memorize and carry later. He doesn't present you to some impostor to which you will give orders... anyway.

The actual benefit I see is with the ifResponds example, where windowdidClose takes an argument. Using @selector-passing way you'd have to create an array or a dictionary of arguments, which is OK in ruby but not that nice with the keyword-message syntax of smalltalk and objective-c...

Nicolas said...

Yes, one of the benefit over using @selector is that the arguments are handled automatically. Without args, @selector could be "good enough" sometimes, yes. And hom is "cleaner" to write while coding.

About the naming problem, I partly agree.. but don't forget (to use marcel's nomenclature) that they aren't "first order messages" but "high order messages", thus the classical naming rules doesn't really apply.. sortof.

But I like the idea anyway. Blocks are something else, and I think both ideas (blocks and hom) are interesting.

Stefan Urbanek said...

Hm, what about pushing some of the concepts to the compiler? No new syntax added, but recognised message patterns that would be optimalised by compiler.

I think that some library functionalities are evolved enough that they can be considered stable. Even Smalltalk is optimalising some message sends into special bytecodes.

Anonymous said...

Still, there is a strange part in the paper, with the adding reports example:
alice addReport: sally reports each.

That implies that addReport: is implemented to handle the trampoline in a specific way, doesn't it?

Similarly, with the concat: examples:
aCollection concat: aString // OK
aString concat: aCollection // concat: reimplemented ?

Nicolas said...

Damien: actually, that's a typo, marcel sent an email about that. Normally it should be :

alice do addReport: sally reports each.

For the concat, what's the problem ? concat (actually, it's stringByAppendingString:) is the normal string method; the HOM method is collect (see fig.5)

Stefan: yes, I was wondering how we could put that directly in the language and/or compiler... and a new syntax to be clearer (like, using commas instead of colons..) ?

All in all, I think it just reflect the necessity in a language to reificate everything -- pieces of codes (blocks), messages (hom), etc. Reflectivity ...