Higher-Order Messages in Cocoaby Rob Rix
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
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
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.
HOM, Sweet HOM
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.
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.
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,
-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
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
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
Let HOM be your mantra
2004-09-17 07:53:31 MikeAmy [View]
Let HOM be your mantra
2005-06-23 01:14:50 DanD. [View]
2004-07-19 11:26:58 mweiher [View]
A couple of notes
2004-07-21 18:53:02 iapole [View]