Sunday, December 13, 2009

The Responder Chain is a Collection

Timothy Wood voices some great ideas on modernizing the Cocoa responder chain. I'd like to venture that if we treat the Responder Chain as a simple collection, a singly-linked list, then such alternatives become easier to model and reason about.


NSEnumerator *responderEnumerator = [[firstResponder mapToNextObjectFromMessage] nextResponder];

I am currently abstracting from the intricate delegate mapping and other ops, these could be handled in an analog fashion. With the enumerator in place, we can obviously snapshot it to get the current state of the responder chain, and also log that.
NSArray *responders = [responderEnumerator allObjects];
NSLog(@"full responder chain:  %@",responders);

Now we can express both current features and possible variations of the Responder Chain architecture compactly as common collection operations. The current dispatch mechanism simply sends the message to the first object that is capable of responding. This corresponds to using the first object of a -select, which is expressed in the -selectFirst convenience method.

Current dispatch

[[[responders selectFirst] respondsToSelector:action] performSelector:action withObject:sender];


If I understood him correctly, Tim wants the objects in the responder chain to return an object that they would like to respond to the message. This turns the -select into a -collect (without a -collectFirst), but is otherwise very similar.

Tim's dispatch

possibleResponders = [[responders collect] responsibleTargetForAction:theAction sender:sender]];
[[possibleResponders objectAtIndex:0] performSelector:action withObject:sender];

I hope this does Tim's ideas justice, but I think the succinct formulation should make it easy to tell wether it does or not.

In terms of combining validation with target/action, I'd be somewhat wary of accidentally triggering actions when validation was meant, though I do appreciate the advantages of combining the two operations. I am not sure what value the block is adding over just having an additional BOOL parameter in the target/action method.


Combined action and validation

typedef BOOL IBAction;
-(IBAction)delete:sender  :(BOOL)onlyValidate
{
    NSArray *selection = [self selectedItems];

   if ( onlyValidate || [selection count] == 0 ) {
        return NO;
   }
   // perform the action
}
// or if you're worried about the naming issues
-(IBAction)delete:sender
{
}

Labels: ,

Wednesday, December 9, 2009

Simple Objective-XML example

Many times now, I've been asked about more Objective-XML examples. Here's a very simple one. It is adapted from Marcus Zarra's very helpful libxml and xmlreader tutorial. That tutorial shows how to parse a very simple XML format using libxml2.

The XML file parsed is the following:

<?xml version="1.0" encoding="UTF-8"?>

<root>

  <person>

    <name>John Doe</name>

    <age>14</age>

  </person>

  <person>

    <name>Mary Doe</name>

    <age>14</age>

  </person>

  <person>

    <name>John Smith</name>

    <age>15</age>

  </person>

</root>

It is parsed using at application startup using the following code:

- (void)applicationDidFinishLaunching:(NSNotification*)notification

{

NSString *path = [[NSBundle mainBundle] pathForResource:@"xmlExample" ofType:@"xml"];

NSData *xmlData = [NSData dataWithContentsOfFile:path];

xmlTextReaderPtr reader = xmlReaderForMemory([xmlData bytes],

[xmlData length], 

[path UTF8String], nil, 

(XML_PARSE_NOBLANKS | XML_PARSE_NOCDATA | XML_PARSE_NOERROR | XML_PARSE_NOWARNING));

if (!reader) {

NSLog(@"Failed to load xmlreader");

return;

}

NSString *currentTagName = nil;

NSDictionary *currentPerson = nil;

NSString *currentTagValue = nil;

NSMutableArray *people = [NSMutableArray array];

char* temp;

while (true) {

if (!xmlTextReaderRead(reader)) break;

switch (xmlTextReaderNodeType(reader)) {

case XML_READER_TYPE_ELEMENT:

//We are starting an element

temp =  (char*)xmlTextReaderConstName(reader);

currentTagName = [NSString stringWithCString:temp

encoding:NSUTF8StringEncoding];

if ([currentTagName isEqualToString:@"person"]) {

currentPerson = [NSMutableDictionary dictionary];

[people addObject:currentPerson];

}

continue;

case XML_READER_TYPE_TEXT:

//The current tag has a text value, stick it into the current person

temp = (char*)xmlTextReaderConstValue(reader);

currentTagValue = [NSString stringWithCString:temp

encoding:NSUTF8StringEncoding];

if (!currentPerson) return;

[currentPerson setValue:currentTagValue forKey:currentTagName];

currentTagValue = nil;

currentTagName = nil;

default: continue;

}

}

NSLog(@"%@:%s Final data: %@", [self class], _cmd, people);

[self setRecords:people];

}

To parse it using MAX you need to add MPWXmlKit and MPWFoundation to your project, and then replace the code above with the following:

- (void)applicationDidFinishLaunching:(NSNotification*)notification

{

NSString *path = [[NSBundle mainBundle] pathForResource:@"xmlExample" ofType:@"xml"];

NSArray *people=[[MPWMAXParser parser] parsedDataFromURL:[NSURL fileURLWithPath:path]];

[self setRecords:people];

}

Labels: , ,

Tuesday, November 10, 2009

Blocked-C II

Damien Pollet thinks my comparison between Objective-C blocks and HOM 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?
These are certainly valid observations, and were some of the reasons that I didn't really think that much of HOM for the first couple of years after coming up with it back in 1997 or so. Since then, I've become less and less convinced that the problems raised are a big concern, for a number of reasons.

Inline vs. Named

One reason is that I actually looked at usage of blocks in the Squeak image, and found that the majority of blocks with at least one argument (so not ifTrue:, whileTrue: and other control structures) actually did contain just a single message send, and so could be immediately expressed as HOMs. Second, I noticed that there were a lot of fairly large (3+ LOC) blocks that should have been separate methods but weren't. That's when I discovered that the presence of blocks actually encourages bad code, and the 'limitation' of HOMs actually was encouraging better(-factored) code.

Of course, I wasn't particularly convinced by that line of reasoning, because it smelled too much like "that's not a bug, that's a feature". Until that is, I saw others with less vested interest reporting the same observation:

But are these really limitations? After using higher order messages for a while I've come to think that they are not. The first limitation encourages you move logic that belongs to an object into that object's implementation instead of in the implementation of methods of other objects. The second limitation encourages you to represent application concepts as objects rather than procedural code. Both limitations have the surprising effect of guiding the code away from a procedural style towards better object-oriented design.
My experience has been that Nat is right, having a mechanism that pushes you towards factoring and naming is better for your code that one that pushes you towards inlining and anonymizing.

Objective-C I

In fact, the Cocoa example that Apple gives for blocks illustrates this idea very well. They implement a "Finder like" sorting mechanism using blocks:

static NSStringCompareOptions comparisonOptions = NSCaseInsensitiveSearch | NSNumericSearch |
        NSWidthInsensitiveSearch | NSForcedOrderingSearch;
NSLocale *currentLocale = [NSLocale currentLocale];
 
NSComparator finderSort = ^(id string1, id string2) {
    NSRange string1Range = NSMakeRange(0, [string1 length]);
    return [string1 compare:string2 options:comparisonOptions range:string1Range locale:currentLocale];
};
 
NSLog(@"finderSort: %@", [stringsArray sortedArrayUsingComparator:finderSort]);

The block syntax is so verbose that there is no hope of actually defining the block inline, the supposed raison d'etre for blocks. So we actually need to take the block out-of-line and name it. So it looks suspiciously like an equivalent implementation using functions:

static NSStringCompareOptions comparisonOptions = NSCaseInsensitiveSearch | NSNumericSearch |
        NSWidthInsensitiveSearch | NSForcedOrderingSearch;
NSLocale *currentLocale = [NSLocale currentLocale];
 
static NSComparisonResult finderSort(id string1, id string2) {
    NSRange string1Range = NSMakeRange(0, [string1 length]);
    return [string1 compare:string2 options:comparisonOptions range:string1Range locale:currentLocale];
};
 
NSLog(@"finderSort: %@", [stringsArray sortedArrayUsingFunction:finderSort context:nil hint:nil]);

Of course, something as useful as a Finder-like comparison sort really deserves to be exposed and made available for reuse, rather than hidden inside one specific sort. Objective-C categories are just the mechanism for this sort of thing:

@implementation NSString(finderCompare)
-(NSSComparisonResult)finderCompare:(NSString*)string2) {
    NSRange myRange = NSMakeRange(0, [self length]);
    return [self compare:string2 options: NSCaseInsensitiveSearch | NSNumericSearch |
        NSWidthInsensitiveSearch | NSForcedOrderingSearch range:string1Range locale:[NSLocale currentLocale]];
}
@end
NSLog(@"finderSort: %@", [stringsArray sortedArrayUsingSelector:@selector(finderCompare:)]);

Note that some of these criticisms are specific to Apple's implementation of blocks, they do not apply in the same way to Smalltalk blocks, which are a lot less noisy.

Objective-C II

Objective-C has at least one other pertinent difference from Smalltalk, which is that it already contains control structures in the basic language, without blocks. (Of course, those control structures can also take blocks as arguments, but these are the different types of blocks that are delimited by curly braces and cannot be passed around as first class objects).

This means that in Objective-C, we already have the ability to do all the iterating we need, mechanisms such as blocks and HOM are mostly conveniences, not required building blocks. If we need indices, use a for loop. If we require keys, use a key-enumerator and iterate over that.

In fact, I remember when my then colleagues started working with a enum-filters, a HOM-precursor that's strikingly similar to the Google Toolbox's GTMSEnumerator+Filter.m. They really took to the elegance, but then also wanted to use it for various special cases. They laughed when they realized that those special-cases were actually already handled better by existing C control structures such as for-loops.

FP, HANDs and Aggregate Operations

While my dislike of blocks is easy to discount by the usual inventor's pride (your child must be ugly for mine to be pretty), that interpretation actually reverses the causation: I came up with HOM because I was never very fond of blocks. In fact, when I first encountered Smalltalk during my university years I was enthralled until I saw the iteration methods.

That's not to say that do:, collect: and friends were not light-years ahead of Algol-type control structures, they most definitely were and still are. Having some sort of higher-order mechanism is vastly superior than not having a higher-order mechanism. I do wish that "higher order mechanism" and "blocks" weren't used as synonyms quite as much, because they are not, in fact, synonymous.

When I first encountered Smalltalk blocks, I had just previously been exposed to Backus's FP, and that was just so much prettier! In FP functions are composed using functionals without ever talking about actual data, and certainly without talking about individual elements. I have always been on the lookout for higher levels of expression, and this was such a higher level. Now taking things down to "here's another element, what do you want to do with that" was definitely a step back, and quite frankly a bit of a let-down.

The fundamental difference I see is that in Smalltalk there is still an iteration, even if it is encapsulated: we iterate over some collection and then execute some code for each element. In FP, and in HOM, there is instead an aggregate operation: we take an existing operation and lift it up as applying to an entire collection.

This difference might seem contrived, but the research done with the HANDS system demonstrates that it is very real:

After creating HANDS, I conducted another user study to examine the effectiveness of three features of HANDS: queries, aggregate operations, and data visibility. HANDS was compared with a limited version that lacked these features. In the limited version, programmers were able to achieve the desired results but had to use more traditional programming techniques. Children using the full-featured HANDS system performed significantly better than their peers who used the limited version.
I also find this difference to be very real.

The difference between iterating with blocks and lifting operations to be aggregate operations also shows up in the fact that the lifting can be done on any combination of the involved parameters, whereas you tend to only iterate over one collection at a time, because the collection and the iteration are in focus.

Symmetry

Finally, the comparison to functional languages shows a couple of interesting asymmetries: in a functional language, higher order functions can be applied both to named functions and to anonymous functions. In essence, the higher order mechanism just takes functions and doesn't care wether they are named or not. Also the higher order mechanism uses the same mechanisms (functions) as the base system,

With block-based higher order mechanisms, on the other hand, we must make the argument an anonymous function (that's what a block is), and we cannot use a named function, bringing us back to the conundrum mentioned at the start that this mechanisms encourages bad code. Not only that, it also turns out that the base mechanism (messages and methods) is different from the higher order mechanism, which requires anonymous functions, rather than methods.

HOM currently solves only the latter part of this asymmetry, making the higher order mechanism the same as the base mechanism, that mechanism being messaging in both cases. However, it currently cannot solve the other asymmetry: where blocks support unnamed, inline code and not named code, HOM supports named but not unnamed code. While I think that this is the better choice in the larger number of cases, it would be nice to actually suport both.

One solution to this problem might be to simply support both blocks and Higher Order Messaging, but it seems to me that the more elegant solution would be to support inline definition of more-or-less anonymous methods that could then be integrated into the Higher Order Messaging framework.

Labels: , , , ,

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"];
    }
];

Labels: , ,

Sunday, September 20, 2009

Cocoa(touch) memory management is as easy as 1-2-3

There is a common misconception that Cocoa memory management is hard. It's not.

  1. Use auto-generated accessors religiously
  2. Release your instance variables in dealloc
  3. Always use convenience methods to create objects
Wow, that wasn't too hard!

Labels: , ,