News: Stay up to date

The Étoilé community is an active group of developers, designers, testers and users. New work is being done every day. Visit often to find out what we've been up to.

News

Higher-order messaging in EtoileFoundation

Posted on 2 July 2009 by Niels Grewe

Over the past weeks, there have been some interesting additions to the EtoileFoundation framework to provide higher-order messaging facilities. Higher-order messaging means having (or at least: having the illusion of having) messages that take messages as their arguments. This can turn out to be quite handy at times. For example, if you are using any class that descends from NSObject, thanks to Quentin you now have a -ifResponds method at your disposal, which frees you from the fear that you might send a message to an object that it doesn't understood.

Borrowing an example from Quentin's documentation of that method, suppose you have cats and dogs in your code zoo where your Dog class implements a -bark method, but the Cat class doesn't. The following naïve approach will then cause an exception to be thrown, because cat does not respond to -bark:

[dog bark];
[cat bark];

If you send the message to -ifResponds, you can make sure that this doesn't happen:

[[dog ifResponds] bark];
[[cat ifRepsonds] bark];

This is neat, especially when you're sending a message to an object that might or might not implement the method but don't care about the case that the message is not understood. Internally you are, of course, not sending a message to a message. -ifResponds rather returns a proxy object that, upon a message send, will forward the message to the original object if it responds to it. Otherwise it will simply return nil.

Another example of higher-order messaging at its best is the -inNewThread method from EtoileThread, which has been around for a while. Any message you send to this will be executed in a new thread, meaning that your programme will only block when you try to access the return value. Sometimes it can be really nice if you don't need to wait for a lengthy operation to complete, especially if you are a hungry cat:

[[dog inNewThread] takeForALongWalk];
[cat feed];

Higher-Order Messaging for Collections

The other addition, which I managed to squeeze into EtoileFoundation with much help and support from David and Quentin, is higher-order messaging related to collections. These higher-order messaging methods make it really easy to manipulate the objects in a collection. One of them is -mappedCollection, which allows you to send messages to every object in the collection, giving you a new collection with the manipulated objects. Since EtoileFoundation provides the useful ETCollection protocol, which aggregates the collection classes from the GNUstep/Cocoa Foundation framework, this works indiscriminately with NSArray, NSDictionary, NSSet, NSIndexSet and their subclasses.

Even if you subclassed one of those yourself and have special needs on how to handle the elements in your custom collection you can still use the existing mechanism by implementing the two methods beginning with -placeObject:. This is already used by some classes, as you can see in the ETCollection+HOM.m source file.

Consider the following example: You have an NSArray named fruitBasket which holds several instances of a class named Fruit which implements a -peeledFruit method to return an instance of its PeeledFruit subclass. How would one solve the problem of peeling all the fruit in the basket? The conventional way would be to enumerate all the objects in the array, sending the -peeledFruit message to each and placing the object returned in a new array, yielding roughly between 5 and 15 lines of code, depending on whether you (can) use fast enumeration and how paranoid you are about sending the message to a wrong object. -mappedCollection helps you to reduce that code to just one line:

NSArray *basketOfPeeledFruit = (NSArray*)[[fruitBasket mappedCollection] peeledFruit];

If fruitBasket had been a mutable collection, you could also have used -map, which modifies the collection directly. There is also another useful method using the same pattern: The -filter method allows you to send a message returning BOOL to each element in the collection to determine whether it should remain in the collection or be removed. This would, for example, allow me to make sure that there are no foul fruit in my basket prior to peeling them. Unfortunately, -filter is only available in a variant that modifies the collection directly, so I have no way to keep the original basket and return a new collection. Why?

The problem with returning a new collection after filtering is, that, again, the whole higher-order messaging stuff is strictly speaking a con. -mappedCollection, -map and -filter (as well as -leftFold and -rightFold, see below) in truth return proxy objects that forward the messages you send to them to each object in the original collection. There is no such thing as higher-order messaging from the compiler's perspective. If you want to return a new collection, everything will work well as long as you only send messages to the proxy that return pointers to objects.

In the above case, the compiler would look at the return value of the method -peeledFruit and allocate enough space to hold a pointer to a PeeledFruit instance. But the proxy object does in fact return an NSArray. This is why you need to cast the return value appropriately, but that is no problem since both are pointers to some area in memory and thus are of the same size. This is not the case if you send a message to a filter proxy. It will return BOOL and the compiler will only allocate enough space for a BOOL return value. If the filter-proxy did return a pointer to the filtered collection, it would not fit into the BOOL-sized portion of memory reserved by the compiler. Terrible things could ensue, and that's why it's better not to do it.

So let's assume our fruitBasket is in fact mutable and the “Fruit”-class implements -isNotFoul (which returns a BOOL-value), we can remove the bad fruit from the basket and peel them afterwards by saying:

[[fruitBasket filter] isNotFoul];
[[fruitBasket map] peeledFruit];

The map operation can also serve as a substitute for the KVC method -valueForKey:. Imagine that every month my phone company will send an array of records for the calls I've placed and received (named callLog). Each record is an NSDictionary containing information about a single call: The names and numbers of caller and callee, duration of the call etc. If I want to get a list of the people I called, I have the following two options, one using higher-order messaging, the other using key-value coding:

NSArray *calledPersons = (NSArray*)[[callLog mappedCollection] objectforKey: @"calleeName"];
NSArray *calledPersons = [callLog valueForKey: @"calleeName"];

Of course, the KVC-variant is arguably more concise, but I'd like to demonstrate how higher-order messaging can help me to find out how much time I spent on the phone this month. I simply extract the “duration”-field from each record:

NSArray *callDurations = (NSArray*)[[callLog mappedCollection] objectForKey: @"duration"];

(Again -valueForKey: could serve the same purpose.) All I need to do now is compute the total of all objects stored in callDurations; a task that can be immensely simplified by two other new methods: -leftFold and -rightFold, which repeatedly invoke a method with an accumulator and each element in a collection, building up the return value in the accumulator. By “left” or “right” you can indicate in what order the elements shall be processed. Since my Duration-class implements -durationByAddingDuration: I can easily find the total this way:

Duration *total = [[callDuration leftFold] durationByAddingDuration: [Duration nullDuration]];

The argument [Duration nullDuration] is used to set up an initial value for the accumulator. I'm setting it to an empty value, since I have no particular use for it. Of course, since addition is a commutative operation, I could have used -rightFold as well. It might also be worth noting that you shouldn't use nil as the initial value of the accumulator, at least for left folds. In left folds the accumulator will be the receiver of the message you send to the proxy object, and if it's set up to nil initially, no message can change that and nil will also be the return value of the operation.

This is nice and convenient, but might not be particularly speedy depending on your use case. Since a message sent to the proxy object goes through the second-chance dispatch mechanism of the Objective-C runtime before it is forwarded to elements of the collection, there is some penalty associated with this initial message send. The more objects there are in your collection, the less likely it is that you will notice it, but in performance critical code you might want to check whether that penalty is still significant.

Using Blocks

But there is also a second area where higher-order messaging might be found wanting. If you are often doing various complex modifications of objects in collections, and want to use -map and friends, you might find yourself adding quite a few methods like -doComplexStuff:withFoo:andBar:andEvenMoreArguments: to your classes, even if you only use them once or twice. If that is the case, and you are lucky enough to have compiled EtoileFoundation with clang (passing the -fblocks switch and linking to the ObjectiveC2 framework), you can also use the new blocks feature of Objective-C 2.0 to achieve something quite similar to higher-order messaging. EtoileFoundation provides analogues to all collection higher-order messaging methods for use with blocks. So we could rewrite the fruit peeling example as follows:

[fruitBasket mapWithBlock: ^(id x){ return [x peeledFruit]; }];

Which, for this simple case, might seem to increase complexity, but is a lot more expressive since the block can include an arbitrary amount of code (and it can even reference variables outside the scope of the block). Additionally, since there is no proxy object needed to which arbitrary messages are sent, there is even a -filteredCollectionWithBlock: method, which is know to return a pointer to an object and won't cause any trouble.

You can get an overview of the implemented methods from the ETCollection+HOM.h header file. I hope that they are going to be useful and that you will have as much fun using them as I had writing them. If you run into any problems please drop me a line. For those interested in more information on higher-order messaging etc. both Marcel Weiher's and Stéphane Ducasse's paper on the topic (PDF) and David's article on Advanced Flow Control for Objective-C are highly recommended.