時代遅れひとりFizzBuzz祭り Objective-C編(C言語とは違うのだよ、C言語とは!)

時代遅れひとりFizzBuzz祭り、今回はObjective-C。前々回のT4 Text Templateとの関連は、何というか双方ともほのかにお仕事の薫りがする所だと勝手に決め付けてみる。

お仕事の薫りといってもCOBOLとかと比べれば微々たるものだけど。

T4 Text TemplateはVisual Studio付属のツールだ。趣味の一環でVisual Studioを使う人はそれなりにいると思うのだが、仕事で使う人はもっと多いはずだ。それにT4 Text Templateを使う場面となるとそれなりに規模の大きいプロジェクトが想定される訳で*1……趣味よりも仕事で遭遇することの方が多いと思う。

Objective-CMac OS XiOS用のアプリケーションの公式開発言語だ。もちろん趣味でそれらのアプリを書いている人は多いと思うけど、同時に仕事として書いている人もいる訳で……特にここ数年はiPhoneiPadアプリの開発案件に関わる人が増えているはずだ。

そんな訳で、個人的には双方ともお仕事絡みの言語という印象が強い。

え、前回のGNU m4との繋がり? 前回は特別編なので繋がりなんてあるはずがない*2のだけど、あえて言えば私にとってはGNU m4もお仕事絡みでの付き合いが多いツールだ。m4というかautoconfだけど。

さて、Objective-CFizzBuzzする訳だが、実は意外と考慮すべき点が多い。

Objective-Cという言語はObjectiveとはいえ所詮はC言語(のスーパーセット)なので、何も考えずに書くとC言語版のFizzBuzzとさして変わらない出来栄えになってしまう。それではObjective-Cを使う意味がない。Objectiveな所を強調したFizzBuzzを書くべきだろう。

C言語のスーパーセットという点も注意が必要だ。というのも標準規格絡みでもK&R C、C89、C95、C99と複数の種類が存在する訳だが、どの規格をベースとするべきだろうか? 更に言えばAppleObjective-Cの処理系はGCCベース*3で、日本語ドキュメント - Apple DeveloperによるとANSI CではなくGNU C/C++の構文のスーパーセットのようだ。実際にXcodeが生成するビルドオプションでは `-std=gnu99' がデフォルトとなっている。ならばせめてC89ではなくC99っぽく攻めるべきだ。

言語本体とは別に、使用するライブラリをどうするかも考えるべきだろう。Objective-Cの言語機能にも注目すべき点は多いが、しかし例えばMacアプリを開発する際に頼もしく感じる点はObjective-Cの言語機能を十二分に生かした便利なツールキット(Cocoa等)が存在することだ。なのでGCC(gobjc)単体で動作するFizzBuzzお茶を濁すのではなく、せめてFoundationフレームワーク程度の機能は使うべきだろう。

Objective-Cのツールキットというと現状ではCocoa等のApple謹製SDKに含まれるものかGNUStepぐらいしか思いつかないのだが、今回はCocoa一択で。XCode 4.0.2のMac OS X v10.6 SDKを使うことにした。なのでObjective-C 2.0で書けるし比較的新しい機能も使える。

まず小手調べにC言語によるFizzBuzzにやや近いバージョンから。Google Objective-Cスタイルガイドのスタイルを真似ている。

#import <Foundation/Foundation.h>

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

static NSString *fizzbuzz(int n) {
  assert(n >= 1);

  NSString *str = (n%3 == 0) ? @"Fizz" : @"";
  if (n%5 == 0) {
    str = [str stringByAppendingString: @"Buzz"];
  }
  if ([str length] == 0) {
    str = [NSString stringWithFormat:@"%d", n];
  }

  return str;
}

int main(void) {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  int i;
  for (i = 1; i <= 100; ++i) {
    (void) printf("%s\n", [fizzbuzz(i) UTF8String]);
  }

  [pool release];

  return EXIT_SUCCESS;
}

やはり文字列型は偉大だ*4。まあ文字列型ではなくFoundationフレームワークのNSStringクラスなのだけど。

ちなみにNSLog()を使わない理由は余分な情報を出力しない為だ。

Objective-CObjectiveに恥じないFizzBuzzを書くとどうなるだろうか? オブジェクト指向プログラミングはよく分からないので間違っているかもしれないが、とりあえずFizzBuzzの値をクラス化してみた。

#import <Foundation/Foundation.h>

#include <stdio.h>
#include <stdlib.h>

@interface FizzBuzzValue : NSObject {
 @private
  int number;
  NSString *answer;
}

+ (id)valueWithNumber:(int)number;
- (id)initWithNumber:(int)number;
- (int)number;
- (NSString *)answer;

@end

@implementation FizzBuzzValue

+ (id)valueWithNumber:(int)number {
  return [[[self alloc] initWithNumber:number] autorelease];
}

- (id)init {
  return [self initWithNumber:0];
}

- (id)initWithNumber:(int)n {
  self = [super init];
  if (self && (n >= 1)) {
    number = n;
    answer = (n%3 == 0) ? @"Fizz" : @"";
    if (n%5 == 0) {
      answer = [answer stringByAppendingString: @"Buzz"];
    }
    if ([answer length] == 0) {
      answer = [NSString stringWithFormat:@"%d", n];
    }
    [answer retain];
  }
  return self;
}

- (void)dealloc {
  [answer release];
  [super dealloc];
}

- (int)number {
  return number;
}

- (NSString *)answer {
  return answer;
}

@end

int main(void) {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  int i;
  for (i = 1; i <= 100; ++i) {
    (void) printf("%s\n", [[[FizzBuzzValue valueWithNumber:i] answer] UTF8String]);
  }

  [pool release];

  return EXIT_SUCCESS;
}

クラスFizzBuzzValueは実質的に値と答えのペアだ。ある値に対するFizzBuzzの答えは(ルールそのものが変更されない限り)変化しないので、オブジェクトの内部状態は初期化時に決定してしまい、後から変更することができないようにしている(つもり)。

何となくFizzBuzzValueをスーパークラスとする「Fizz/Buzz/FizzBuzz/その他」の4種類のサブクラスを定義した方がよい気がしないでもないが、面倒だし名前の付け方に困ったので止めた*5

これを書いてからメソッドnumberとanswerはプロパティとして定義してしまえば楽なことに気づいて、泥縄式に直した。

#import <Foundation/Foundation.h>

#include <stdio.h>
#include <stdlib.h>

@interface FizzBuzzValue : NSObject {
 @private
  int number;
  NSString *answer;
}

@property (readonly) int number;
@property (readonly) NSString *answer;

+ (id)valueWithNumber:(int)number;
- (id)initWithNumber:(int)number;

@end

@implementation FizzBuzzValue

@synthesize number;
@synthesize answer;

+ (id)valueWithNumber:(int)number {
  return [[[self alloc] initWithNumber:number] autorelease];
}

- (id)init {
  return [self initWithNumber:0];
}

- (id)initWithNumber:(int)n {
  self = [super init];
  if (self && (n >= 1)) {
    number = n;
    answer = (n%3 == 0) ? @"Fizz" : @"";
    if (n%5 == 0) {
      answer = [answer stringByAppendingString: @"Buzz"];
    }
    if ([answer length] == 0) {
      answer = [NSString stringWithFormat:@"%d", n];
    }
    [answer retain];
  }
  return self;
}

- (void)dealloc {
  [answer release];
  [super dealloc];
}

@end

int main(void) {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  int i;
  for (i = 1; i <= 100; ++i) {
    (void) printf("%s\n", [[[FizzBuzzValue valueWithNumber:i] answer] UTF8String]);
  }

  [pool release];

  return EXIT_SUCCESS;
}

プロパティの導入に関しては色々と議論があったらしいが、アクセサメソッドが自動的に生成されるという点はメリットだろう。

もう1つクラスを追加してみる。FizzBuzzのまとまった解答を生成するクラスだ。

#import <Foundation/Foundation.h>

#include <stdio.h>
#include <stdlib.h>

@interface FizzBuzzValue : NSObject {
 @private
  int number;
  NSString *answer;
}

@property (readonly) int number;
@property (readonly) NSString *answer;

+ (id)valueWithNumber:(int)number;
- (id)initWithNumber:(int)number;

@end

@implementation FizzBuzzValue

@synthesize number;
@synthesize answer;

+ (id)valueWithNumber:(int)number {
  return [[[self alloc] initWithNumber:number] autorelease];
}

- (id)init {
  return [self initWithNumber:0];
}

- (id)initWithNumber:(int)n {
  self = [super init];
  if (self && (n >= 1)) {
    number = n;
    answer = (n%3 == 0) ? @"Fizz" : @"";
    if (n%5 == 0) {
      answer = [answer stringByAppendingString: @"Buzz"];
    }
    if ([answer length] == 0) {
      answer = [NSString stringWithFormat:@"%d", n];
    }
    [answer retain];
  }
  return self;
}

- (void)dealloc {
  [answer release];
  [super dealloc];
}

@end

@interface FizzBuzz: NSObject {
 @private
  int upperLimit;
  NSMutableArray *answers;
}

@property (readonly) int upperLimit;

+ (id)fizzBuzzWithUpperLimit:(int)upperLimit;
- (id)initWithUpperLimit:(int)upperLimit;
- (NSArray *)answers;

@end

@implementation FizzBuzz

@synthesize upperLimit;

+ (id)fizzBuzzWithUpperLimit:(int)upperLimit {
  return [[[self alloc] initWithUpperLimit:upperLimit] autorelease];
}

- (id)init {
  return [self initWithUpperLimit:0];
}

- (id)initWithUpperLimit:(int)limit {
  self = [super init];
  if (self && (limit >= 1)) {
    upperLimit = limit;
    answers = [NSMutableArray arrayWithCapacity:(NSUInteger)limit];
    int i;
    for (i = 0; i < limit; ++i) {
      [answers addObject:[FizzBuzzValue valueWithNumber:i+1]];
    }
    [answers retain];
  }
  return self;
}

- (void)dealloc {
  [answers release];
  [super dealloc];
}

- (NSArray *)answers {
  NSMutableArray *tmp = [NSMutableArray arrayWithCapacity:(NSUInteger)upperLimit];
  for (FizzBuzzValue *val in answers) {
    [tmp addObject:val.answer];
  }
  return [NSArray arrayWithArray:tmp];
}

@end

int main(void) {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  NSArray *answers = [[FizzBuzz fizzBuzzWithUpperLimit:100] answers];
  [answers enumerateObjectsUsingBlock:
    ^(id item, NSUInteger index, BOOL *stop) {
      (void) printf("%s\n", [(NSString *)item UTF8String]);
    }
  ];

  [pool release];

  return EXIT_SUCCESS;
}

クラスFizzBuzzは1から指定した上限値までFizzBuzzした結果を保持する。現状は最小限の実装となっていて、上限値を後から変更できないし、結果の文字列のコレクションを返すanswersぐらいしか有用なメソッドが用意されていない。

コレクションを舐めるのに2種類の方法を使用している。1つは高速列挙で、クラスFizzBuzzのメソッドanswersにて使用している。もう1つがNSArrayのメソッドenumerateObjectsUsingBlockで、面白そうだったのでmain()にて使っている。

Objective-CにもFoundationフレームワークにもまだまだ興味深い点が多々あるが、ここまでのソース程度でも実装していて結構面白く感じた。

よく言われるように、Objective-Cの「Objective」たる部分はC言語とは別物となっている。私自身はSmalltalkの経験が無いのでアレだが、知っている言語の中ではC++よりもRubyの雰囲気に近いように感じる。とはいっても、実際にはRubyとも大きく異なる言語な訳で……今までに経験した言語にはないものを感じる。

以前に聞いた話だが、Objective-CのObjectiveな機能はC言語とは別物であるが故に、オブジェクト指向プログラミング未経験の人がC言語からObjective-Cに切り替えると、当初は生産性がガクッと低下するらしい。しかしある程度慣れてくると学習曲線が急上昇するという。これがC++(のオブジェクト指向的な部分)の場合、生産性はさほど低下しないものの、学習曲線の上昇はObjective-Cよりも緩やかだという。

私は未だにオブジェクト指向プログラミングはよく分からないのだが、C++RubyJavaScriptをかじっていたからか、Objective-CのObjectiveな部分に対する違和感はそれほど感じなかった。たった1週間コードを書いただけでも意外と手に馴染んだように感じた。*6

まあ誕生したのが1980年代半ばである為か、モダンなプログラミング言語と比べると見劣りする部分はあるのだが――それでもObjective-Cは結構良い言語だと思う。ツールキットを含めてMaciOS以外の開発でも広く使用できたらもっとよいのになあ。

*1:いや、別に小規模プロジェクトで使えない訳じゃなくて、小規模ならゴリゴリとコードで書いてしまっても何とかなるのでそうしてしまう人が多い気がする。

*2:アニメの劇場公開版がテレビシリーズ本編と繋がりがない(ことが多い?)のと同じ。

*3:最近はLLVMか?

*4:C言語には文字列型といえるような機能は存在しないので。C言語の文字列は乱暴に言うと「ヌル終端している文字の配列」で、しかもC言語の配列はデータ型としては二級市民だ。C++のstd::stringやMFCのCStringをありがたく感じるのは、Cプログラマ特有のメンタリティのたまものかもしれない。

*5:あと「それはやり過ぎだ」とゴーストがささやいたので。

*6:それよりも参照カウンタ方式によるメモリ管理云々の流儀の方が未だに慣れない。