自己構文拡張・マルチパラダイム言語

id:ranhaに影響されて、言語方面に興味を持ちつつある今日この頃、Objective-Cをちょっと触ってみたので紹介してみます。


Objective-Cの特徴(WikiPedia:Objective-C):

  • 静的型付けと動的型付けの両方をサポート
  • コンパイル時型チェックが効く
  • ダックタイピングもできる
  • ガベージコレクション
  • 動的なクラスの追加、動的なメソッドの追加、method_missing
  • CとC++をそのまま書ける
  • 古くて枯れている


C++と比べられることが多いようですが、Objective-Cオブジェクト指向機能はC++よりもRubyに近いものです。
gccgcc-objcパッケージ)でコンパイルでき、LinuxFreeBSD、Windowsなど各種OSで動きます。
NS〜なライブラリもGNUStepに実装されており、普通に使える言語です。Mac/iPhone専用言語というわけではないです :-p


何はともあれ、Objective-C的に Hello World! を書いてみると↓こんな感じになります。

#import <objc/Object.h>
#import <iostream>

// クラスの宣言。普通はヘッダに書く
@interface TestClass : Object
{	// インスタンス変数の定義
	const char* string;
}

// id型を返し、const char*型の引数を取るインスタンスメソッド
-(id)setString:(const char*)str;

// id型を返し、std::ostream&型の引数を取るインスタンスメソッド
-(id)printString:(std::ostream&)stream;
@end


// クラスの実装
@implementation TestClass
-(id)setString:(const char*)str
{
	string = str;  // 渡された引数をインスタンス変数にセット
	return self;
}

-(id)printString:(std::ostream&)stream
{
	stream << string << std::endl;  // インスタンス変数をstreamに出力
	return self;
}
@end


// main()関数
int main(int argc, char* argv[])
{
	// TestClassのインスタンスを確保して初期化
	id test = [[TestClass alloc] init];

	// setStringメソッドを呼び出す
	[test setString:"Hello, World!"];

	// printStringメソッドを呼び出す
	[test printString:std::cout];

	return 0;
}


コンパイル & 実行

$ g++ -lobjc hello.mm
$ ./a.out
Hello, World!


C++の中に別の言語が融合したような、極めてカオスな様相が見て取れます。
インラインでC/C++が書けるとかそういうレベルではなく、Objective-Cのメソッドの引数にC++のオブジェクトを渡したり、C++のクラスのメンバ変数にObjective-Cインスタンスが入っているとか、激しいレベルで融合が可能なマルチパラダイム言語と言えます。


↓こんな感じでダックタイピングもできます。

#import <objc/Object.h>
#import <iostream>

// FooクラスとBarクラスの両方にprintメソッドを定義
@interface Foo : Object
-(void)print;
@end

@implementation Foo
-(void)print { std::cout << "Foo" << std::endl; }
@end


@interface Bar : Object
-(void)print;
@end

@implementation Bar
-(void)print { std::cout << "Bar" << std::endl; }
@end

// ダックタイピング
void print(id obj)
{
	[obj print];
}

int main(int argc, char* argv[])
{
	id foo = [[Foo alloc] init];
	id bar = [[Bar alloc] init];

	print(foo);  //=> Foo
	print(bar);  //=> Bar

	return 0;
}


動的にクラスやメソッドを追加したり、Rubyのmethod_missing的機能は、ダイナミックObjective-C - マイコムジャーナルにとても詳しく載っています。ランタイムの実装まで踏み込んだ詳しい解説でスバラシイ。
Objective-Cの普通の使い方はWikiPedia:Objective-CAppleのドキュメントあたりに書いてあります。

Objective-Cプリプロセッサ

なぜ突然Objective-Cを使ってみたかと言えば、もうC++には疲れたのです…。
静的型付けでGCも無いし、templateで無理矢理ダックタイピングするとgccがBus Errorで落ちる…。


とても気持ちよく書けるRubyに希望を持っているのですが、どうしてもC++と比べると遅いので、ガツガツパフォーマンス出したいぜーイェーィという時は使えない。それから型チェックがまったく効かないので、まれに困ることがある。


そこで、Objecitve-C。


しかしObjective-Cは構文が冗長であるというイタイ欠点があります。インスタンスメソッドに「-」を使うのは良いとしても、メソッド呼び出しにいちいち「[」を付けるのはちょっと…。それから@implementationはあまりに長い。

というわけで、Objective-Cプリプロセッサがあれば嬉しいというアイディア。

自己構文拡張言語

ここからやっと本題なのですが、ただObjective-Cプリプロセッサを書くだけではおもしろく無いので、強力なマクロ機能があると、実はかなり実用的なのではないかと思っています。

NemerleDylanなどには、プログラムの中で新たな構文を追加する機能があります。この機能をプリプロセッサに持たせれば、プログラムの中に直接XMLを書く構文を追加したり、SQLが突然出てくるコードが書けるハズ。


↓こんなイメージ。

// インラインXML構文用の構文拡張ファイルを読み込む
@extend "xml"

int main(void)
{
  // インラインXML
  id xml = <root>
    <item attr="10" />
  </root>;

  // 2種類の文字列
  const char* cstr = "C-style String";
  id str = @"Objective string";

  // メソッド呼び出し
  str :substr 0 10;

  // メソッドチェーン
  str :substr 0 10 :chomp;

  // C++と融合(メソッド呼び出しの構文が違うので融合可能)
  str :substr foo.get();

  // 結合性の変更(from Haskell)
  str :substr foo.get() $ :inc;
}


Objective-Cへの変換

int main(void)
{
  id xml = [[SomeXMLClass alloc] init:"<root><item attr="10" /></root>"];

  const char* cstr = "C-Style String";
  id str = [[String init] alloc:"Objective string"];

  [str substr :0 :10];

  [[str substr :0 :10] chomp];

  [str substr :foo.get()];

  [str substr :[foo.get() inc]];
}


しかし実装するにはBNFを書くのが辛すぎる。うーむ…