自己構文拡張・マルチパラダイム言語
id:ranhaに影響されて、言語方面に興味を持ちつつある今日この頃、Objective-Cをちょっと触ってみたので紹介してみます。
Objective-Cの特徴(WikiPedia:Objective-C):
- 静的型付けと動的型付けの両方をサポート
- コンパイル時型チェックが効く
- ダックタイピングもできる
- ガベージコレクション
- 動的なクラスの追加、動的なメソッドの追加、method_missing
- CとC++をそのまま書ける
- 古くて枯れている
C++と比べられることが多いようですが、Objective-Cのオブジェクト指向機能はC++よりもRubyに近いものです。
gcc(gcc-objcパッケージ)でコンパイルでき、Linux、FreeBSD、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-CやAppleのドキュメントあたりに書いてあります。
Objective-Cプリプロセッサ
なぜ突然Objective-Cを使ってみたかと言えば、もうC++には疲れたのです…。
静的型付けでGCも無いし、templateで無理矢理ダックタイピングするとgccがBus Errorで落ちる…。
とても気持ちよく書けるRubyに希望を持っているのですが、どうしてもC++と比べると遅いので、ガツガツパフォーマンス出したいぜーイェーィという時は使えない。それから型チェックがまったく効かないので、まれに困ることがある。
そこで、Objecitve-C。
しかしObjective-Cは構文が冗長であるというイタイ欠点があります。インスタンスメソッドに「-」を使うのは良いとしても、メソッド呼び出しにいちいち「[」を付けるのはちょっと…。それから@implementationはあまりに長い。
というわけで、Objective-Cのプリプロセッサがあれば嬉しいというアイディア。
自己構文拡張言語
ここからやっと本題なのですが、ただObjective-Cのプリプロセッサを書くだけではおもしろく無いので、強力なマクロ機能があると、実はかなり実用的なのではないかと思っています。
NemerleやDylanなどには、プログラムの中で新たな構文を追加する機能があります。この機能をプリプロセッサに持たせれば、プログラムの中に直接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を書くのが辛すぎる。うーむ…