ValaによるGNOMEアプリケーションのプログラミング
GNOMEのプログラミング言語Valaを使えば、わざわざANSI Cでオブジェクト指向的なプログラミングをしなくても、GNOMEデスクトップのコア部分であるGLib2オブジェクトシステムを利用できる。MonoやJavaとは異なり、Valaのプログラムには仮想マシンもランタイムライブラリも必要なく、Valaオブジェクトを利用する人々はそれらがC以外の言語で書かれていることを知る必要さえない。
Valaコンパイラのvalacは、ValaのコードをCのコードに変換し、その結果をgccでコンパイルすることによってオブジェクトコードを生成する。従来のC言語環境で高水準言語を使おうとすると、言語のバインドという大きな問題に直面する。具体的には、それらの出所、メンテナンス状況、バグの有無などだ。バインドの質が低いと、高水準言語の利用はC言語以上にストレスを感じさせるものになり得る。ValaにはGLibのイントロスペクションが使えるツール群が含まれており、任意のGLib2オブジェクトでValaバインドを生成できる。ValaのFedora 9用パッケージには、GLib2、GTK+2、SDL、SQLite、WebKit、libsoup、libglade-2、hildon、hal、gstreamer、cairo、dbusの各種バインドが含まれている。GLib2/GTK+2ベースのデスクトップやハンドヘルドアプリケーションで必要になりそうなライブラリの多くは、すでにValaアプリケーションで利用可能なわけだ。Valaプロジェクトは、Valaバインドを提供するプロジェクトのリストも用意している。
ValaはC言語コードの利用に重きを置いている。それ以外についてはどうだろうか。Valaでは、C/C++開発者が簡単にオブジェクトを再利用できるように、GLib2オブジェクトのヘッダファイルを生成することができる。GLib2オブジェクトについて記述したCのヘッダファイルが扱えるものなら、どんな言語でもValaオブジェクトを利用できるはずなので、Valaで実装されたオブジェクトはPerlやPythonでも使えるはずだ。
GLib2では、シグナルとプロパティを用いて独自のクラスを定義できる。Cで書かれたGLib2プログラムの場合は、class_init関数でg_signal_new
を使うことで、GObjectに対して新たなシグナルを関連付けて登録する。この新しいシグナルには、固有の数値IDであるsignalIDを与える。シグナルを発行するには、g_signal_emit
関数を使って、そのsignalIDと、必要に応じてほかのパラメータも渡す。新しいシグナルの登録は非常に煩雑で、シグナルの呼び出しには数値のsignalIDを使う必要があるため、シグナルを発行する行では静的な型チェックが十分に行えない。
以下のCコードでは、最初に“test-signal1”という新しいシグナルをGLib2オブジェクトに登録している。notify
は、このシグナルへの接続が可能が関数で、test-signal1シグナル発行時の変更への対応に使われる。g_signal_connect
関数の実行により、test-signal1シグナルが発行されるたびにnotify関数の呼び出しが行われるようになる。最後の行では、このシグナルを実際に発行しており、その結果notify関数が呼び出される。
... class_init() { ... g_test_signals[TEST_SIGNAL1] = g_signal_new ("test-signal1", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GTestClass, test_signal1), NULL, NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); ... } ... static void notify (GObject *object, GParamSpec *spec, gpointer user_data) { gint value; g_object_get (object, "test-prop", &value, NULL); g_print ("+ %d", value); } ... test1 = g_object_new (G_TYPE_TEST, NULL); g_signal_connect (test1, "test-signal1", G_CALLBACK (notify), NULL); ... g_signal_emit (G_OBJECT (test), g_test_signals[TEST_SIGNAL1], 0, 0);
一方、次のコードは、やはりGLib2のシグナルtest_signal1
を定義して利用するValaのコードだ。シグナルのシグネチャは先ほどのCのコードと同じだが、引数として整数を1つ取り、返り値は持たない。このシグナルがほぼメソッドと同じ形で宣言されていることに注意してほしい。パラメータの定義は、G_TYPE_NONE/G_TYPE_INT
といったマクロではなく、コードのほかの部分と同様の型が使われている。また、最後の行におけるシグナルの発行も、関数の呼び出し構文によって行われている。シグナルに対して“+=
”演算子を用いている行では、引数を標準出力に書き出すだけのハンドラ関数がインラインで定義されている。
public class Test : GLib.Object { public signal void test_signal1(int a); public static void main(string[] args) { Test t1 = new Test(); t1.test_signal1 += (t, a) => { stdout.printf("%d\n", a); }; t1.test_signal1(5); } }
上記のValaコードはかなり簡潔だが、実際には先ほどのCコードよりも多くの処理を行っている。このValaのソースコードだけで、カスタムのGLib2クラスを使った実行可能なプログラムになっているのだ。
なお、シグナルを発行する行ではValaによって静的な型チェックが行われる。そのため、この行の引数を文字列に変えてtest_signal1を発行しようとすると、次のようなコンパイルエラーが出る。
$ valac -o signal signal.vala signal.vala:12.25-12.29: error: Argument 1: Cannot convert from `string' to `int' t1.test_signal1("foo"); ^^^^^ Compilation failed: 1 error(s), 0 warning(s)
Vala言語の特徴は、オブジェクトの作成やシグナルの定義および扱いを簡単にしたことだけではない。Valaはローカル変数の型推論もサポートしており、「 var foo = getFoo();
」とすればGLib2のエラーが例外に変わるほか、foreach
を使うことで、GLibのC言語APIよりもずっと楽にコレクションに対する繰り返し処理が行える。
GLib2のオブジェクトを利用したプログラムを書くのに、foreach構文やラムダ関数といった言語設計上、比較的新しい機能を持つ言語が使えるのは、すばらしいことだ。Valaでは自作オブジェクトのヘッダファイルを生成することができる。そのため、ほかの言語を使っている開発者には、その実装の中身に対する検討を強いることなく、オブジェクトを使ってもらえる。
ただ、できるだけシンプルかつ気持ちよくGLib2プログラムを書けるようにする特徴を備えていながら、Vala言語にはそれを学ぶための参考書はもちろん、ドキュメント類も少ないのが玉にキズだ。
Ben Martinは10年以上前からファイルシステムに携わっている。博士号を持ち、現在はlibferris、各種ファイルシステム、検索ソリューションを中心としたコンサルティング業務に従事。