Next article: Friday Q&A 2010-02-12: Trampolining Blocks with Mutable Code
Previous article: Friday Q&A 2010-01-29: Method Replacement for Fun and Profit
Tags: blocks continuations errorhandling fridayqna objectivec
The Earth has moved 6.9 degrees around the Sun since my last post, which means it's time for another edition of Friday Q&A. This 6.9-degree segment, Guy English has suggested that I talk about the use of continuation passing style to simplify error returns in Objective-C code.
NSError **
The standard Cocoa convention for signalling errors to a caller is to return the error by way of an extra parameter pointing to a NSError *
variable, like so:
NSError *error;
NSString *string = [[NSString alloc] initWithContentsOfFile: path encoding: NSUTF8StringEncoding error: &error];
if(!string)
// do something with error
nil
as two separate steps. The error parameter is also optional, so the method needs to do a NULL
check for every error case, like so:
if(failure)
{
if(error)
*error = [NSError errorWith...];
return nil;
}
Other languages handle this quite a bit nicer. For example, the equivalent in Python is:
string, error = someFunction(...)
if failure:
return None, createErrorObject(...)
Exceptions
For languages without support for multiple return values (and often for those that do support it), the common solution for error returns is exceptions. In Objective-C, using this technique would look like this for the caller:
@try
{
NSString *string = [[NSString alloc] init...];
// use string
}
@catch(NSException *exception)
{
// handle exception
}
@try
block, and share the same @catch
handler.
The other end of it is nice and simple:
if(failure)
@throw [NSException exceptionWith...];
However, exceptions have their own problems. If a method can return an error and you ignore it, then you just lose out on some diagnostic information. If a method can throw an exception and you fail to catch it, then it can cause inconsistent states and even crashes as the exception is thrown through code that's not written to handle it. Writing exception-safe code requires more thought and care.
These problems aren't insurmountable. Exceptions are often used in other languages for this sort of thing. However, they're not traditionally used in Objective-C, which means that even if you want to use them, it can be tough to deal with all the code out there that isn't aware of them. For routine errors, it will also make it difficult to debug your programs when you end up having a serious error where an exception shouldn't be thrown at all, but is. It's very common to simply break on objc_exception_throw
to, for example, figure out just where an exception is being thrown from within some Cocoa calls, but that technique becomes unusable if your application frequently throws exceptions as part of its routine operations.
If you don't want to use exceptions, but want something better than NSError **
, what can you do?
Continuation Passing Style
Continuation Passing Style, or CPS, is a style of programming using anonymous functions to replace return statements. Instead of returning a value, a function will take another function as a parameter. Then, when it gets to the point where it would have returned a value, it calls the passed-in function with the value as a parameter instead. In Objective-C, we now have anonymous functions in the form of blocks, so CPS can be achieved using blocks.
Here's an example of how CPS looks. This is the standard-style code:
NSString *string = [obj stringWhatever];
// use string
[obj stringWhatever: ^(NSString *string) {
// use string
}];
Thus, the annoying error return-by-reference style can be converted into a cleaner CPS version, like this:
[NSString stringWithContentsOfFile: path encoding: NSUTF8StringEncoding continuation: ^(NSString *string, NSError *error) {
if(error)
// handle error
else
// use string
}];
On the other side it's pretty simple to deal with as well:
+ (void)stringWithContentsOfFile: (NSString *)path encoding: (NSStringEncoding)encoding continuation: (void (^)(NSString *, NSError *))continuation
{
// create string from path
if(failure)
continuation(nil, [NSError errorWith...]);
else
continuation(string, nil);
}
- (void)computeObject: (id)obj continuation: (void (^)(id, NSError *))continuation
{
[NSString stringWithContentsOfFile: path encoding: NSUTF8StringEncoding continuation: ^(NSString *string, NSError *error) {
if(error)
continuation(nil, error);
else
{
...continue processing with string...
id result = ...;
continuation(result, nil);
}
}];
Two-Block Variant
Since the first action of the continuation is almost certain to be to check and do something different if an error occurred, it would be reasonable to split up the continuation into two parts. This moves the check into the called method (which probably needs to have such a check anyway) and simplifies the caller. You pass an error block and a normal block, and the method will then call the appropriate one. This variant would look like this:
[NSString stringWithContentsOfFile: path encoding: NSUTF8StringEncoding
errorHandler: ^(NSError *error) {
// handle error
}
continuation: ^(NSString *string) {
// use string
}];
- (void)computeObject: (id)obj errorHandler: (void (^)(NSError *))errorHandler continuation: (void (^)(id))continuation
{
[NSString stringWithContentsOfFile: path encoding: NSUTF8StringEncoding errorHandler: errorHandler continuation: ^(NSString *string) {
// continue processing with string
continuation(string);
}];
}
Using CPS for error returns isn't all roses. A problem comes at the interface between CPS code and normal-style code. In other words, where you have a method which needs to return a value, but which calls CPS methods. The problem arises because using the
return
statement inside a block returns a value from the block, not from the enclosing function:
- (NSString *)contentsOfFile: (NSString *)path
{
[NSString stringWithContentsOfFile: path encoding: NSUTF8StringEncoding
errorHandler: ^(NSError *error) {
[NSApp reportError: error];
}
continuation: ^(NSString *string) {
return string; // fails! returns string from the block, not the method!
}];
}
__block
-qualified local variable, then return from within the main function body:
- (NSString *)contentsOfFile: (NSString *)path
{
__block NSString *returnValue = nil;
[NSString stringWithContentsOfFile: path encoding: NSUTF8StringEncoding
errorHandler: ^(NSError *error) {
[NSApp reportError: error];
}
continuation: ^(NSString *string) {
// string is retained here in case there are any inner autorelease pools
// in the NSString method that's calling this continuation
// retaining it will keep it alive even if there is one and it is popped
// the retain is balanced by an autorelease in the method body
returnValue = [string retain];
}];
return [returnValue autorelease];
}
A bigger downside is that, well, Cocoa doesn't do CPS error returns, it does return-by-reference. It's not too hard to write adapter methods:
typedef void (^ErrorHandler)(NSError *error);
@implementation NSString (CPSErrors)
+ (void)stringWithContentsOfFile: (NSString *)path encoding: (NSStringEncoding)encoding errorHandler: (ErrorHandler)errorHandler continuation: (void (^)(NSString *string)continuation
{
NSError *error;
NSString *string = [self stringWithContentsOfFile: path encoding: encoding error: &error];
if(string)
continuation(string);
else if(errorHandler)
errorHandler(error);
}
@end
Conclusion
The addition of blocks to Objective-C enables some completely new ways of doing things, including a new way to deal with errors. The practicality of this approach remains to be seen, but it certainly does produce nicer-looking code than the traditional Cocoa way.
That's it for this Friday Q&A. Come back in 5.56×1015 periods of the radiation corresponding to the transition between the two hyperfine levels of the ground state of the caesium 133 atom for the next exciting installment. Friday Q&A is driven by user submissions, so if you have a topic you would like to see covered here, send it in!
Comments:
First, the two-callback-based system that Twisted Deferreds use ("callback" and "errback"): http://twistedmatrix.com/documents/9.0.0/api/twisted.internet.defer.Deferred.html) is an asynchronous implementation of what you describe, but it makes sense to see it used synchronously too.
NSWorkspace
does have a couple of methods that provide an NSError
to the callback (recycleURLs:
and duplicateURLs:
), as in your first example, so there is some possibility that this style will be used more in future.
- (void)recycleURLs:(NSArray *)URLs completionHandler:(void (^)(NSDictionary *newURLs, NSError *error))handler AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER;
- (void)duplicateURLs:(NSArray *)URLs completionHandler:(void (^)(NSDictionary *newURLs, NSError *error))handler AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER;
Finally, your comment about
return
not returning from the containing function made me laugh—I was just reading a blog entry where someone was trying to address Smalltalk having the opposite problem. Of course, Smalltalk being Smalltalk, it was actually possible to work around this by adding a method: http://www.cincomsmalltalk.com/userblogs/mls/blogView?entry=3441567670.I'd rather type 10 lines than have unreadable code. :(
NSWorkspace
methods aren't exact analogs, because they're using CPS for asynchronicity, and if you're writing asynchronous code then you have to use a block, function pointer, object/selector pair, etc. callback, and pass the results into it. Using blocks for synchronous continuations is fairly different, although this does show that Apple isn't afraid of this sort of thing.
Interesting note about Smalltalk. What happens if you use the
^
return operator after the enclosing scope has already returned?
jrk: One man's elegance is another man's unreadable, I suppose. Care to elaborate on why you find this to be unreadable?
- (BOOL)doSomething
{
NSError *error = nil;
id obj1 = [obj objectWhateverWithError:&error];
if(obj1 == nil)
{
[self _handleError:error]
return NO;
}
id obj2 = [obj1 objectWhateverWithError:&error];
if(obj2 == nil)
{
[self _handleError:error]
return NO;
}
id obj3 = [obj2 objectWhateverWithError:&error];
if(obj3 == nil)
{
[self _handleError:error]
return NO;
}
return YES;
}
How this method would look like in CPS style?
- (BOOL)doSomething
{
__block BOOL returnValue = NO;
[obj objectWhateverWithErrorHandler: ^(NSError *) {
[self _handleError: error];
}
continuation: ^(id obj1) {
[obj1 objectWhateverWithErrorHandler: ^(NSError *) {
[self _handleError: error];
}
continuation: ^(id obj2) {
[obj2 objectWhateverWithErrorHandler: ^(NSError *) {
[self _handleError: error];
}
continuation: ^(id obj3) {
returnValue = YES;
}];
}];
}];
return returnValue;
}
The nesting does get a bit ugly, but I don't find it to be too horrible. In reality, I would probably rewrite it as a series of smaller methods:
- (BOOL)doSomething
{
__block BOOL returnValue = NO;
[obj objectWhateverWithErrorHandler: ^(NSError *) {
[self _handleError: error];
}
continuation: ^(id obj1) {
returnValue = [self doSomethingWithObj1: obj1];
}];
return returnValue;
}
If you want to get fancy, take advantage of the fact that your three error handlers are all identical, and define that handler up front so you just pass a variable in to each one:
- (BOOL)doSomething
{
__block BOOL returnValue = NO;
void (^errorHandler)(NSError *) = ^(NSError *) { [self _handleError: error]; };
[obj objectWhateverWithErrorHandler: errorHandler continuation: ^(id obj1) {
[obj1 objectWhateverWithErrorHandler: errorHandler continuation: ^(id obj2) {
[obj2 objectWhateverWithErrorHandler: errorHandler continuation: ^(id obj3) {
returnValue = YES;
}];
}];
}];
return returnValue;
}
I find this to look really nice, but I don't know how likely it would be to find a situation where you could use it in real code.
I really like the idea of CPS, but I think (at least with current syntax) it decreases the readability of the code. In first example, the indentation level (which is actually conditional nesting level in this case) grows from 1 to 4, in second example from 1 to 2 and requires the developer to break the method into smaller ones, which may or may not be desirable (if the original method is already performing a single semantic thing and all method calls are on the same abstraction level, it may not make sense to break it down).
I'm not sure if that already addresses your question about the return operator before, but you can write a method #street like this in a class Person:
Person>>street
| address |
address := addresses at: self ifAbsent:[^nil].
^address street
If there is no address for the given person, it'll execute the error-block. In that block is a return, so the #street method returns nil. If an address was found, the #at:ifAbsent: would return this address and would not evaluate its error-block.
Thus the method does only return after "address street" is evaluated.
Person>>something
^[^nil]
In other words, you return the block from the enclosing method, then call it somewhere later, but it still contains a
^
statement. I know this probably makes no sense to do intentionally, but I'm wondering what happens if you try it anyway.
Given the discussion about Smalltalk, I'm finding myself wishing that Objective-C blocks had the same semantic where the block's value is simply the value of its last expression. Having to write return everywhere is annoying and sometimes confusing.
stringWithContentsOfFile:encoding:errorHandler:continuation:
I don't really see the advantage to having the continuation block at all! If I don't error give me back the string and let my program continue its normal flow, but let the block handle any errors.
How does that second block help me out?
Thanks for the great posts.
NSString *myString = [obj ...message];
if(myString)
{
// it's good
}
Versus:
[obj ...message: ^(NSString *myString) {
// it's good
}];
The rest is basically the same amount of code, so you save the if statement. You also lose the convenience of having code flow return directly to your method body....
As for the implicit return of blocks. That would be great, but that just doesn't work in the c-based Objective-C, where you explicitly need to declare the return type.
Objective-C blocks are already doing return type inference. Declaring the return type of a block is possible but not mandatory. This inference could be used for implicit returns as well. Really, implicit return is just a bit of syntactic sugar in this case. It's just like putting the
return
keyword right before the last statement in the block.http://gist.github.com/239363
Python is able to return a list or tuple. Automatic unpacking allows you to unpack the array in multiple values:
aTuple = ("value1", "value2", "value3", "value4")
value1, *value2and3, value4 = aTuple
# Is the same as
aTuple = "value1", "value2", "value3", "value4"
value1, *value2and3, value4 = aTuple
# Is the same as
def aFunction():
return "value1", "value2", "value3", "value4"
value1, *value2and3, value4 = aFunction()
$.ajax({
type: "POST",
url: "some.php",
data: "name=John&location=Boston",
success: function(msg){
alert( "yay!" );
},
error: function(xr, s, e) {
alert( "aww :(" );
}
});
http://api.jquery.com/jQuery.ajax/
throws
it's hard to adopt :(
https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html
If the last non-block parameter of an Objective-C method is of type NSError **, Swift replaces it with the throws keyword, to indicate that the method can throw an error. If the Objective-C method’s error parameter is also its first parameter, Swift attempts to simplify the method name further, by removing the “WithError” or “AndReturnError” suffix, if present, from the first part of the selector. If another method is declared with the resulting selector, the method name is not changed.
If an error producing Objective-C method returns a BOOL value to indicate the success or failure of a method call, Swift changes the return type of the function to Void. Similarly, if an error producing Objective-C method returns a nil value to indicate the failure of a method call, Swift changes the return type of the function to a nonoptional type.
func handle<T>(_ call: @autoclosure () throws -> T, errorHandler: (Error) -> Void, continuation: (T) -> Void) {
do {
let value = try call()
continuation(value)
} catch {
errorHandler(error)
}
}
enum E: Error {
case e
}
func f() throws -> String {
return "Hello, world"
}
func g() throws -> String {
throw E.e
}
handle(try f(), errorHandler: {
print("error: \($0)")
}, continuation: {
print("success: \($0)")
})
handle(try g(), errorHandler: {
print("error: \($0)")
}, continuation: {
print("success: \($0)")
})
However, I think Swift's error handling system is good enough to obsolete this whole idea.
Comments RSS feed for this page
Add your thoughts, post a comment:
Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.