On writing apps,

by Nicolas Bouilleaud.

2015-02-09

This is Objective-C:

@interface NSArray (Map)
DefineMethod(map, id (^)(id), NSArray*);
DefineMethod(reduce, id (^(^)(id))(id), NSArray*);
@end

@interface NSString (Funcs)
DefineProperty(uppercaseString, NSString*);
DefineMethod(plus, NSString*, NSString*);
@end

int main()
{
    Print(@[@"foo", @"bar", @"baz"].map(NSString.uppercaseString));
    // -> ( FOO, BAR, BAZ )

    Print(@"Hello".plus(@" I love you"));
    // -> Hello I love you

    Print(@[@"foo", @"bar", @"baz"].reduce(NSString.plus));
    // -> foobarbaz
    
    InstanceProperty massUppercaser = NSArray.map(NSString.uppercaseString);
    Print(massUppercaser(@[@"abc",@"def"]));
    // -> ( ABC, DEF )

    InstanceProperty exclamate = NSString.plus(@"!!");
    Print(@[@"quick", @" to the batmobile"].map(NSString.uppercaseString).map(exclamate).reduce(NSString.plus));
    // -> QUICK!! TO THE BATMOBILE!!
}

Yes, it compiles and runs.

The NSArray.map, NSArray.reduce, and NSString.plus methods are implemented as regular objective-C methods:

@implementation NSString (Plus)
- (NSString*) plus:(NSString*) string
{
    return [self stringByAppendingString:string];
}
@end

The main idea is simply to define additional, parameter-less methods (e.g. -[NSString plus] instead of -[NSString plus:]) that return a block, and that blocks takes a parameter that’s used as the parameter of the actual method. It’s okay if you need to reread that sentence, but really, it’s not that complicated.

Of course, writing those block wrapper implementations for each and every method would be painful. Lucky for us, the objective-C runtime lets us add methods on demand with resolveInstanceMethod. This was almost surprisingly straightforward.

The whole gist is here. The usual warnings apply: this is a hack and probably full of bugs, don’t start doing anything serious with it.

A few random notes/things I learned/thoughts I had along the way:

On Functional Programming:

  • exclamate = NSString.plus(@"!!"); is effectively a partial application of the NSString.plus function. It produces a new function, with one parameter less. (Is it Currying or Partial Application? I never know, my understanding is that it’s basically the same thing for functions with 2 or less arguments.)
  • I’m a functional programming noob (or rather I forgot what I learned in school.). These block properties are probably called monoids or something.
  • I first saw this technique in OCMock 2, where it enables a simpler, shorter syntax.

On the ObjC runtime:

  • To add class methods at runtime in Objective-C, one actually adds instance methods to the metaclass. The class methods of a class are the instance methods of its metaclass.
  • Similarly, resolveClassMethod could largely be ignored. When resolveClassMethod fails, the runtime calls resolveInstanceMethod on the metaclass.
  • I only coded support for object (id) argument and return types, because I care about my mental health. I had started experimenting with methods with >1 arguments, using NSInvocation an va_args, but this was getting out of hand.
  • I’m swizzling [NSObject resolveInstanceMethod], because YOLO. This is incredibly powerful: this is just like patching the dispatch mechanism of the language.
  • Clang supports the dot notation for properties as well as argument-less methods, including Class methods. [Class method] can be written Class.method. However, no autocomplete for these.

On Objective-C and language design:

  • I’ve said it before, Objective-C is really malleable. All in all, it doesn’t complain too much: dynamic languages FTW. The less enjoyable part of it all is, as usual, the fucking block syntax.
  • It’s fun to use a language with a coding style that is completely different from what it’s been designed for. However, if you take a look at mid-90s Objective-C source files and compare it to recent UIKit, you’ll see that it doesn’t really seem like the same language. In old-style ObjC, the id type was implied for arguments and return types (it still works), and methods often returned self instead of void.
  • Higer Order Messaging isn’t really a new concept in the Objective-C community. Of course, there’s the KVC collection operators such as in [items valueForKeyPath:@"@sum.price"]; the very existence of NSInvocation is also proof that this had been a field of interest for the language maintainers a long time ago.
  • Before UIKit, before stackoverflow, there was this page on cocoadev.com: http://cocoadev.com/HigherOrderMessaging
  • I wonder what the Target-Action paradigm of Cocoa would look like if it was implemented with Functional Programming in mind. I guess we’ll see in a few years. Also, why are you all excited about this UXKit thing in Photos.app?
  • One final thought: I implemented block methods as readonly properties. What if methods were readwrite properties? We could write NSString.plus = <something else> at runtime, either for the whole class or for a specific instance.

Anyway, it looks I’m ready for Swift. Can we have more dynamism please?