Higher-Order Messages in Cocoa
by Rob Rix07/16/2004
Just about the first step any programmer takes past "Hello World" is tackling arrays. In C, arrays are pretty pitiful things because C chooses not to insulate the programmer from the mechanism used -- the bytes and bytes of physical memory.
Fortunately, Mac developers have Cocoa, and Cocoa has NSArray. It protects you from many of the dreaded off-by-one errors -- or at least lets you know when and where one has happened. And if you use proper exception handling (and you know you should!) you can often cope with these problems when they arise.
But you still need a way of getting at the things in the array. You can use NSArray's objectAtIndex:, or the higher-level (yet speedy) NSEnumerator, which also gives you the benefit of being able to swap out the array for a set without changing much code.
No matter what method you use, iterating over the elements of an array gets a bit repetitive. At the end of a year of programming, you'll have written so many for and while loops that you'll catch your fingers trying to type them any time somebody even says the word "again."
Loops are with us because they are useful. Removing for or while from the language would be disastrous! However, there are situations where you just know there's got to be something better than iterating through a loop to do what you want to do.
Enter HOM.
HOM, Sweet HOM
|
Related Reading
Cocoa in a Nutshell |
HOM, or Higher-Order Messaging, refers to the treatment of a message as a data type, like an object, so that it can be used as the argument in another message. This is analogous to languages that treat functions as a first-class data type, like Lisp, Haskell, and PHP do. Even C and its descendants can use function pointers, although it's not as pretty.
In languages like Smalltalk, HOM is implemented with lexical closures, also known as blocks. Blocks let you write, inline, a small chunk of code that can, for instance, be passed along to every object in a collection, visiting and operating on each one.
Some of the later specs for Objective-C included blocks (Brad Cox called them "action expressions" in his paper, TaskMaster), which would be absolutely ideal for HOM of all sorts. But NeXT, and later Apple, never adopted these.
So what do we do if we want HOM in Objective-C/Cocoa? There are two basic options: blocks and trampolines.
Building Blocks
First, blocks. I know that I just said that Apple's Objective-C doesn't include blocks, and that remains true. However, Joe Osborn has written a class that parses blocks from a string passed in. This gives you the benefit of being able to write code such as [anArray do:ocblock(:each | [each doSomething]; [self doSomethingWith:each];)]. Complex blocks may be a little bit tricky, and at its heart it is based on an interpreter, so it may be a bit slower than you'd like in some circumstances, but there is no doubt that it is both an inventive and capable solution.
More information can be found on CocoaDev's OCBlock page, and the framework, OSFoundation, which contains OCBlock, can be downloaded from Joe Osborn's iDisk; the disk image contains both the framework and the sources.
Bouncing Balls
Second, trampolines. Trampolines are a fairly simple, if not extremely common concept. They are objects that bounce received messages to a predefined target. In order to be comfortable with trampolines, you should have some experience with the Objective-C runtime and the forwarding of messages -- unless you don't mind it all seeming like magic.
Of course, this article is directed at those who prefer to understand the magic, so to give a brief explanation, let's see what a trampolined message might look like:
[[bigRedDogs select] isNamed:@"Clifford"];
In this excerpt, there are two messages, -select and -isNamed:. The first is the higher-order message; the message that takes (in a sense) another message.
What exactly is happening here? Well, first, we'll state the assumption that bigRedDogs is some sort of collection. In that case, -select would be implemented (most likely in a category) to return a trampoline object, a proxy standing in for bigRedDogs itself.
That trampoline, then, receives the message -isNamed:, and since it doesn't implement it, the Objective-C runtime is called upon to a) encode the message into an instance of NSInvocation, and b) call -forwardInvocation: on the trampoline with that invocation as the argument.
Now, since the trampoline was initialized in -select, it can be customized to the task at hand -- set up to call another method, -select: (note the colon -- this method takes an argument!) on bigRedDogs. So when -forwardInvocation: is called, it will in turn call the specified method, -select:, on its target with the invocation as the argument.
From here on in, there's little magic (until you get into how returns are handled, which is an exercise left to the reader -- you can find it on CocoaDev without too much trouble). -select: just has to go over the elements in the receiver, invoking the invocation on each of them in turn, and returning an array consisting of those objects that responded with YES (or some non-zero value, in some systems; typically this would involve methods returning nil for negative answers).
This is just an example of one implementation of trampolines. As a counterexample, I recommend taking a look at Mike Amy's rather inspired take on them, CCDMessageDistributer [sic], which packages higher-order operations as objects for much simpler addition of new actions. This is perhaps not as efficient as the method described here, but it wins hands-down in terms of not only coolness but the aura of "the right thing."
So what's the use here? To answer that, I would like to provide you with a quote by the much-adored Douglas Adams: "I am rarely happier than when spending an entire day programming my computer to perform automatically a task that it would otherwise take me a good ten seconds to do by hand."
Humor aside, there is more than just a principle at stake here. Yes, we as programmers like to have code we can use and reuse. But the real argument here comes in three parts: first, that the cumulative effects of those repeated ten seconds could easily add up to more than the time spent writing the once-and-only-once solution if we've got a lengthy timescale. Second, that we can optimize our loops more easily if they are internalized like this. And my personal favorite, the third -- that we can be assured of being rid of one-off errors. All we have to do is get it right once.
There are many more uses for HOM than iteration, but I've found methods such as -select (returning the objects that respond in the affirmative), -reject (returning the objects that respond in the negative), -collect (returning the objects' responses), -detect (returning the first object that responds in the affirmative), and -do (simply performing some action -- although I prefer a less imperative style of coding in general, this can be undeniably useful) to be the most commonly used higher-order methods in my repertoire.
But I won't make you leave without hinting at some of the possibilities. How about a -sum message for methods returning int, float, NSNumber, or objects conforming to some protocol? The possibilities are endless -- the truth is out there.
Tell your friends: find a loop, and internalize it. It's a guerilla campaign, but it's freedom at stake. But that said, I'm interested in conflicting viewpoints. So, aside from sheer efficiency for tight inner loops (which maybe ought to be using C and/or C++ code anyway, be they inside a method or not), what do you think?
Rob Rix is a renaissance man masquerading as a specialist, and is Canadian to boot.
Return to the Mac DevCenter
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 6 of 6.
-
Let HOM be your mantra
2004-09-17 07:53:31 MikeAmy [Reply | View]
-
Let HOM be your mantra
2005-06-23 01:14:50 DanD. [Reply | View]
Just wondering, if superHeros is an array, why not just use
[superHeros makeObjectsPerformSelector:@(saveTheDay)];
or, alternately
{
[superHeros makeObjectsPerformSelector:@(defeatTheVillain)];
[superHeros makeObjectsPerformSelector:@(saveTheGirl)];
}
-Dan -
Let HOM be your mantra
2005-06-23 01:17:49 DanD. [Reply | View]
dang it, I meant
[superHeros makeObjectsPerformSelector:@selector(saveTheDay)];
etc. Sorry for not proofreading.
-Dan -
Let HOM be your mantra
2005-06-23 16:40:08 georgerix [Reply | View]
(I'm posting from my Dad's account for the moment--apologies for any confusion this causes.)
There really is no problem with makeObjectsPerformSelector:; it's a handy mechanism to have when you need to perform an operation over an array. However, it doesn't yield the full flexibility of a mechanism such as blocks or trampolines, whereby you can define methods (like collect, select, reject, and detect) which return (and in the case of detect, operate) differently.
makeObjectsPerformSelector: is more or less equivalent, when combined with its siblings which take further arguments to use, to the HOM method do, but more complex uses require invocations... trampolines and HOM are basically a system to make invocations automatically-- that is, to make them conveniently.
As much as anything else, it's a matter of style.
Thanks for the comments!
-
A couple of notes
2004-07-19 11:26:58 mweiher [Reply | View]
Great to see this idea being promoted without having to do it myself! :-))
However, I should point out that Higher Order Messaging and blocks (or ActionExpressions) are distinct mechanisms. Blocks are not an implementation of Higher Order Messaging.
In fact, HOM was specifically developed as an alternative to blocks/closures/higher-order-functions in a language where blocks aren't available, and the name was chosen to make clear that this is a higher-order mechanism based on messaging instead of (anonymous) functions.
A presentation held at MacHack a while back gives some more background info, though it is admittedly a bit terse: http://www.metaobject.com/papers/HOM-Presentation.pdf.
Furthermore, it does seem a bit odd that the original and definitive implementation of HOM, MPWFoundation, isn't mentioned.
Marcel
-
A couple of notes
2004-07-21 18:53:02 iapole [Reply | View]
Well, talk about silly things. The article was posted and I immediately went on vacation, and on my return I received a couple of very polite e-mails mentioning my article and the fact that I totally forgot to mention your work.
Holy _cow_ am I ever sorry. To make it clear, I used MPWFoundation as a reference for my own trampolines implementation back in the day, and it was in fact your work that inspired me to come up with this. And I remembered that and planned to write about it but it slipped my mind. My deepest apologies Marcel, you deserve full credit for this.
As to Blocks/HOM, it may be my more recent experience with languages such as Io which blurred the line for me. You're perfectly right, but I've always seen Blocks as being inline methods, so it made sense to me.
Thank you very much for the correction and your patience! Your work on MPWFoundation opened a lot of conceptual doors for me.






The inspiration is really owed to Haskell's map and fold functions. I just translated the idea into obj-c, using existing HOM implementations as a guide.
It is true that HOM reduces LOC, increases readability and reduces errors. But, I think the most important benefit is in what happens to the design.
Consider a simple while loop iterating over an array such as:
[1]
NSEnumerator *superHeroEnumerator = [superHeros objectEnumerator];
SuperHero *superHero;
while (superHero = [superHeroEnumerator nextObject])
{
[superHero defeatTheVillain];
[superHero saveTheGirl];
}
Note the while block has several statements. Several statements don't naturally go into one HOM statement. Instead we are forced to add the block onto the target. (We can always do this in Objective-C thanks to categories. Respect 'em.)
So we now have:
[2]
@implementation SuperHero
- (void) saveTheDay
{
[self defeatTheVillain];
[self saveTheGirl];
}
@end
with our main loop being:
{
[[superHeros all] saveTheDay]
}
What happened? The block became a method on the target! Exactly where it should be. SuperHeros should know how to save the day. They don't need someone to tell them how (which is the case in [1].
Of course, we could have been a bit more insightful at the outset and used:
[1a]
while (superHero = [superHeroEnumerator nextObject])
[superHero saveTheDay];
with -saveTheDay already defined as in [2]. We could then have applied the loop refactoring as before.
What this shows us is that two refactorings have occurred:
1. The block moved to the target as a method ("Extract method").
2. The loop was refactored using - all.
In this case the block refactoring was done first. The point is that doing the loop refactoring first forces the block refactoring.
HOM encourages the Law of demeter by forcing blocks onto the things that perform them. Each axis of change has been extracted in [2]. The code is much more flexible in the face of changes, which is a good thing.
NB The other alternative was to have:
[1b]
{
[[superHeros all] defeatTheVillain];
[[superHeros all] saveTheGirl];
}
as our main loop.
I'll leave it as an exercise for the reader to figure out in what situations [1b] is better or worse.
Enjoy - Mike