Objective-C で流れるようなインタフェース

Underscore.js にインスパイアされた Objective-C のライブラリ Underscore.m を試してみたんだけど、 その使い方にちょっと驚いた。 流れるようなインタフェースで書けるじゃないか。 例えば、公式ページのサンプル。

NSDictionary *dictionary = @{
  @"en": @"Hello world!",
  @"sv": @"Hej världen!",
  @"de": @"Hallo Welt!",
  @"ja": [NSNull null]
}

NSArray *capitalized = Underscore.dict(dictionary)
  .values
  .filter(Underscore.isString)
  .map(^NSString *(NSString *string) {
    return [string capitalizedString];
  })
  .unwrap;

Objective-C っぽく無い。 これだけ見ると、JavaScript の Underscore.js を使っていると錯覚してしまいそうだ。

どうやって実装しているか興味が沸いたのでソースコードを見てみた。

一部抜粋。

@interface USArrayWrapper : NSObject

...

@property (readonly) USArrayWrapper *(^map)(UnderscoreArrayMapBlock block);
@property (readonly) USArrayWrapper *(^filter)(UnderscoreTestBlock block);

...

@end

@implementation USArrayWrapper

...

- (USArrayWrapper *(^)(UnderscoreArrayMapBlock))map
{
    return ^USArrayWrapper *(UnderscoreArrayMapBlock block) {
        NSMutableArray *result = [NSMutableArray arrayWithCapacity:self.array.count];

        for (id obj in self.array) {
            id mapped = block(obj);

            if (mapped) {
                [result addObject:mapped];
            }
        }

        return [[USArrayWrapper alloc] initWithArray:result];
    };
}

- (USArrayWrapper *(^)(UnderscoreTestBlock))filter
{
    return ^USArrayWrapper *(UnderscoreTestBlock test) {
        return self.map(^id (id obj) {
            return test(obj) ? obj : nil;
        });
    };
}

...

@end

なんと、Blocks を返す読み取り専用プロパティだったのか。 確かにプロパティまたは引数無しメソッドなら . で呼び出せるし、 Blocks なら ( ) で引数渡せる。

よく考えたなぁ。真似しようとは思わないけど。