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. WhenresolveClassMethod
fails, the runtime callsresolveInstanceMethod
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, usingNSInvocation
anva_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 writtenClass.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 returnedself
instead ofvoid
. - 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?