Friday, November 6, 2009

Blocked-C

Update: It appears that the original article has been removed, and has been superseded by material at: http://developer.apple.com/mac/articles/cocoa/introblocksgcd.html. The original article had more on the Cocoa block APIs and gave a refreshingly honest assessment of the for-loop vs. Block-iteration comparison.

While the news that Apple is adding blocks to C and Objective-C in the SnowLeopard time frame has been around for some time, a recent article shed some light on the actual API.

While there probably are some places where Objective-C blocks can be useful, I am not really impressed. In the following samples, red is used to show noise, meaning code that is just there to make the compiler happy.



NSMutableArray *filteredItems= [NSMutableArray array];
[items enumerateObjectsWithOptions:0 withBlock:
    ^(id item, NSUInteger index, BOOL *stop) {
        [filteredItems addObject:[item stringByAppendingString:@"suffix"]];
    }
];

As you can see, the version using blocks is very, very noisy, both syntactically and semantically, especially compared with the HOM version:
[[items collect] stringByAppendingString:@"suffix"];

No prizes for guessing which I'd prefer. To put some numbers on my preference: 234 characters vs. 52, 19 tokens vs. 3, 5 lines vs. 1. In fact, even a plain old C for-loop is more compact and less noisy than our "modern" blocked version:
NSMutableArray *filteredItems= [NSMutableArray array];
for (int i=0; i < [items count]; i++ ) {
     [filteredItems addObject:[items objectAtIndex:i] stringByAppendingString:@"suffix"];
    }
];

4 comments:

Damien said...

But your example is not completely fair… from my (Smalltalk) experience, the block passed to #collect: is often not a single message send, but rather a small adhoc expression, for which it does not really make sense to define a named method. Or you might need both the element and its key/index… how does HOM deal with that?

Marcel Weiher said...

Damien: this is a valid point that I have actually thought about quite a bit...probably worth another post.

Anonymous said...

Aren't you just arguing against the API and possibly the syntax, rather than the concept of first-class closures?

A proper API would support:

NSArray *new = [items map: ^(id item) {
return [id stringByAppendingString: @"suffix"];
}];

An improved, type-inferring syntax would support something like:

val new = [items map i =>[i stringByAppendingString: @"suffix"]];

As an earlier commenter noted, single message sends are unusual, and generally speaking you'll want a broader expression. That it's to say that HOM isn't valuable, but I don't see why it's an either/or proposition.

Additionally, if you can't see more than "some" places blocks are useful and you're not impressed, you just aren't looking very hard.

Apple's closure syntax may not be the best, but you very easily can model quite a few complex ideas on top of them -- lazily, recursively evaluated sequences, simple sequence comprehensions, futures ...

Marcel Weiher said...

Anonymous: yes, it is Objective-C blocks and some of the APIs that go with them that I am particularly unimpressed with. Smalltalk blocks for example are certainly less noisy and more useful, and something like Backus's FP is downright gorgeous (if possibly less useful).

What you are missing is that any broader expression can be given a name and converted into a HOM. In functional languages, this is more obvious because HOFs usually take either named or anonymous functions. It is only in OO languages that we accept this weird notion that all Higher Order processing must be using anonymous functions whereas normal processing is accomplished via named messages that invoke named methods. Odd.

All the capabilities you think I am not seeing are ones that can be and have been implemented with HOM. IMHO more elegantly than with blocks. And maybe the answer is that we do need both blocks and HOM, but it's not the case that you need blocks to get those types of features.