2015/06/22

高機能なマイナープログラミング言語Valaについて

本記事は、現時点ではマイナーだが優れたプログラミング言語Valaについて書かれたものとなる。

元の記事は2009年4月に書かれたが、2つに分かれていた記事を統合した上で多数の記述を追加している。ただし、言語の仕様や機能などを全て扱うことはできないため、内容が偏っていたり不足していたりする可能性がある。

  1. Valaについて
  2. Vala言語の機能(一部)
  3. Vala/Genieで使用可能なライブラリ
  4. コードのコンパイル
  5. 簡単なコード例

Valaについて

  • Vala言語は、C言語でオブジェクト指向プログラミングを行う仕組みを提供するGObjectライブラリ[1]を用いてC#言語に近い文法で記述できるオブジェクト指向言語
  • Valaのコンパイラ[2]を用いると、Vala言語もしくはGenie言語という独自言語によって記述されたプログラムからC言語の中間コードを作り、それをC言語のコンパイラに渡してOSネイティブな実行形式が作られる
    • コンパイルしてできた実行形式はGObjectライブラリに依存するが、元々同ライブラリを用いるGTK+などを使用したプログラムは依存ライブラリが増えることがない
    • C++言語と異なり、実行形式のABI互換性が頻繁に問題になることはない(C言語と同じ扱い)
    • Mono/.NETやJavaはソースがOSやアーキテクチャに非依存な形式に変換されるが実行時の動作が遅く、これに対しValaのプログラムはOSネイティブな実行形式として動作し、OSやアーキテクチャに依存するが高速に動作する
  • GObjectライブラリをC言語で直接用いるのは扱いにくいが、Valaのプログラムから使われる際にはこの使い方を意識する必要はなく、コードの生産性も高い
  • Valaコンパイラのパッケージは公式サイト(英語)で配布されているが、ディストリのパッケージとしてインストールできる場合が多い
    • Debian/Ubuntuでは “valac(-バージョン)” パッケージでコンパイラがインストールできる
  • C#で使える “基本クラスライブラリ” に相当するものはなく、文法が近いといってもライブラリ部分の関係で扱い方はかなり異なる
    • GLibやGIOのライブラリが色々な機能を提供しているが、これだけでは基本クラスライブラリとは同等ではなく、外部ライブラリを言語バインディングから用いる場面もある
  • 既存の統合開発環境(IDE)の幾つかはValaに対応しているが、Valamaという次世代のIDEが開発されている
    • Val(a)IDEと呼ばれるIDEが以前開発されていたが、開発は停滞して公式サイトもなくなった

Vala言語の機能(一部)

  • C#言語に近い文法
    • 基本的な文法やデータ型が似ている
    • 名前空間,構造体,クラスなど
    • for,switch,while,do-while,if,foreachなどの構文
    • switch文における文字列による分岐
    • ローカル変数の型推論(varキーワード)
    • 例外の扱い
    • コンストラクタの書き方に独特な部分がある(名前付きコンストラクタ)
    • 特定の型を中身とするリスト型などが標準で利用できるが、libgeeライブラリを用いることでこれらよりも高度なコレクションなどの機能が利用できる
  • オブジェクト指向プログラミング
    • クラスとその継承
    • インターフェース
    • プロパティ
  • メモリ管理は参照カウントの方式で動作
    • オブジェクトは波括弧内から出たときなど、変数が寿命になったところで他から参照されていない限り自動的かつ即座に破棄される
      • デストラクタに資源の開放処理を記述すると寿命になったタイミングでその処理が行われる
      • GIOライブラリのファイル入出力関係のクラスをValaで用いるとそのオブジェクトが寿命になると自動的に閉じられ、開放処理を手動で記述しない形のコードになる
    • 参照の循環には注意が必要で、弱い参照(“weak” や “unowned” を付ける)を用いる必要のある場面がある
  • GObjectシグナル(イベント)の扱いに関する機能
  • UTF-8エンコーディングの文字列型
  • スレッド
  • 無名関数/クロージャ
    • 外側で宣言されたローカル変数が内側の無名関数から参照可

詳しい情報はチュートリアル(英語)を参照。

Vala/Genieで使用可能なライブラリ

  • 言語バインディングの定義ファイル(.vapiファイル)を通してC言語のライブラリ[3]の機能を用いることができる
    • 各ライブラリの開発ファイルもコンパイル時に別途必要
    • .vapiファイルはライブラリを用いるC言語ソースを出力するためのものなので、言語バインディングが用意されていないライブラリ用のバインディングを比較的容易に自作でき、C言語で直接記述するよりもコードの可読性が向上したり資源開放処理の記述が省ける分コードが書きやすかったりする
  • 多数のライブラリの言語バインディングがValaのコンパイラに同梱されている[4]
    • Vala言語向けのリファレンスはライブラリごとにValadocで参照できる他、詳しい説明などはC言語向けのリファレンスを参考にすることもできる(いずれも基本的に英語)
  • GLibライブラリの中の機能はGLibの.vapiファイルを通して常に使用可能
    • 文字列を組み立てるのにはStringBuilderクラスや[フォーマット文字列].printf()が便利
    • 正規表現はGLibの機能(GLib.Regexクラス)として利用可
    • 数学に関する関数や定数などはGLib.Math名前空間で利用可
    • リストやキューなどのデータ型もクラスとして利用可
    • 詳しくはGLibのリファレンスなども参照
  • C言語の標準Cライブラリは “posix” の.vapiファイルを通して常に使用可能
  • GTK+などのGObjectに基づいたライブラリは.vapiファイルを用いてオブジェクト指向のライブラリとして使用でき、クラスの継承もできる
  • .vapiファイルを用いる場合、そのファイルで使用されている名前空間を用いることができ、using文を用いるとその名前空間内の関数やクラスなどを名前空間を指定せずに使用できるようになる
    • 名前空間 “GLib” は常に名前空間を指定せずに使用可
    • C#にある、代入する形のusing文(別名・エイリアスの指定)の書き方は使用できない
    • 同じ名前のクラスなどを含む複数の名前空間にusing文を使用した場合、名前空間を指定せずにそれを記述するとどちらかが決まらないことが出るため、名前空間を指定した書き方が必要となる[5]
  • オブジェクト指向でファイル入出力を行うにはGIOライブラリ(GLibの一部)が使える
  • vala-extra-vapisでは更に多数のライブラリの言語バインディングが提供されているが、後方互換性が保証されない形で提供されるため、この中の言語バインディングを用いたコードが将来のバージョンの言語バインディングでは動かなくなる可能性がある点には注意が必要(コピーして使用するプログラム側に同梱することが推奨されている)。
Vala 0.28時点の標準付属の.vapiファイルを一覧する作業例
[vala-0.28.0/vapi]$ ls *.vapi
alsa.vapi                         gtksourceview-2.0.vapi
atk.vapi                          gtksourceview-3.0.vapi
atspi-2.vapi                      gudev-1.0.vapi
avahi-client.vapi                 hal.vapi
avahi-gobject.vapi                hildon-1.vapi
bzlib.vapi                        hildon-fm-2.vapi
cairo-xcb.vapi                    json-glib-1.0.vapi
cairo.vapi                        libarchive.vapi
ccss-1.vapi                       libbonoboui-2.0.vapi
clutter-1.0.vapi                  libdaemon.vapi
clutter-gdk-1.0.vapi              libepc-1.0.vapi
clutter-gst-1.0.vapi              libesmtp.vapi
clutter-gst-2.0.vapi              libftdi.vapi
clutter-gst-3.0.vapi              libgda-4.0.vapi
clutter-gtk-0.10.vapi             libgda-report-4.0.vapi
clutter-gtk-1.0.vapi              libgdata.vapi
clutter-json-1.0.vapi             libglade-2.0.vapi
clutter-x11-1.0.vapi              libgnome-2.0.vapi
cogl-1.0.vapi                     libgnome-menu-3.0.vapi
cogl-pango-1.0.vapi               libgnome-menu.vapi
config.vapi                       libgnomeui-2.0.vapi
curses.vapi                       libgsf-1.vapi
dbus-glib-1.vapi                  libgvc.vapi
enchant.vapi                      libmagic.vapi
fuse.vapi                         libnl-1.vapi
gconf-2.0.vapi                    libnl-2.0.vapi
gdk-2.0.vapi                      libnl-3.0.vapi
gdk-3.0.vapi                      libnotify.vapi
gdk-pixbuf-2.0.vapi               liboobs-1.vapi
gdk-x11-2.0.vapi                  libosso.vapi
gdk-x11-3.0.vapi                  libpanelapplet-2.0.vapi
gdl-1.0.vapi                      libpeas-1.0.vapi
gdl-3.0.vapi                      libpeas-gtk-1.0.vapi
gdu-gtk.vapi                      libpq.vapi
gdu.vapi                          librsvg-2.0.vapi
gedit-2.20.vapi                   libsexy.vapi
gedit.vapi                        libsoup-2.2.vapi
geocode-glib-1.0.vapi             libsoup-2.4.vapi
gio-2.0.vapi                      libusb-1.0.vapi
gio-unix-2.0.vapi                 libusb.vapi
glib-2.0.vapi                     libvala-0.28.vapi
gmodule-2.0.vapi                  libwnck-1.0.vapi
gnet-2.0.vapi                     libwnck-3.0.vapi
gnome-desktop-2.0.vapi            libxml-2.0.vapi
gnome-keyring-1.vapi              linux.vapi
gnome-vfs-2.0.vapi                loudmouth-1.0.vapi
gnutls.vapi                       lua.vapi
gobject-2.0.vapi                  mx-1.0.vapi
gobject-introspection-1.0.vapi    mysql.vapi
goocanvas.vapi                    orc-0.4.vapi
gsl.vapi                          packagekit-glib2.vapi
gstreamer-0.10.vapi               pango.vapi
gstreamer-1.0.vapi                pangocairo.vapi
gstreamer-allocators-1.0.vapi     pixman-1.vapi
gstreamer-app-0.10.vapi           poppler-glib.vapi
gstreamer-app-1.0.vapi            posix.vapi
gstreamer-audio-0.10.vapi         purple.vapi
gstreamer-audio-1.0.vapi          raptor.vapi
gstreamer-base-0.10.vapi          rasqal.vapi
gstreamer-base-1.0.vapi           readline.vapi
gstreamer-cdda-0.10.vapi          rest-0.6.vapi
gstreamer-check-0.10.vapi         rest-0.7.vapi
gstreamer-check-1.0.vapi          rest-extras-0.6.vapi
gstreamer-controller-0.10.vapi    rest-extras-0.7.vapi
gstreamer-controller-1.0.vapi     sdl-gfx.vapi
gstreamer-dataprotocol-0.10.vapi  sdl-image.vapi
gstreamer-fft-0.10.vapi           sdl-mixer.vapi
gstreamer-fft-1.0.vapi            sdl-net.vapi
gstreamer-interfaces-0.10.vapi    sdl-ttf.vapi
gstreamer-net-0.10.vapi           sdl.vapi
gstreamer-net-1.0.vapi            sqlite3.vapi
gstreamer-netbuffer-0.10.vapi     taglib_c.vapi
gstreamer-pbutils-0.10.vapi       tiff.vapi
gstreamer-pbutils-1.0.vapi        tokyocabinet.vapi
gstreamer-riff-0.10.vapi          tracker-indexer-module-1.0.vapi
gstreamer-riff-1.0.vapi           twitter-glib-1.0.vapi
gstreamer-rtp-0.10.vapi           udisks2.vapi
gstreamer-rtp-1.0.vapi            unique-1.0.vapi
gstreamer-rtsp-0.10.vapi          v4l2.vapi
gstreamer-rtsp-1.0.vapi           vte-2.90.vapi
gstreamer-rtsp-server-1.0.vapi    vte.vapi
gstreamer-sdp-0.10.vapi           webkit-1.0.vapi
gstreamer-sdp-1.0.vapi            webkit2gtk-4.0.vapi
gstreamer-tag-0.10.vapi           webkit2gtk-web-extension-4.0.vapi
gstreamer-tag-1.0.vapi            x11.vapi
gstreamer-video-0.10.vapi         xcb-icccm.vapi
gstreamer-video-1.0.vapi          xcb.vapi
gtk+-2.0.vapi                     xtst.vapi
gtk+-3.0.vapi                     zlib.vapi
gtkmozembed.vapi

コードのコンパイル

  • コンパイラのコマンド名はvalac(-[バージョン])
  • 出力ファイル名は-oオプションで指定可
  • C言語ソースを出力するには-Cオプションを付ける
  • スレッドを用いたプログラムのコンパイル時には--threadオプションを付ける
  • 外部のパッケージを用いる場合は--pkg [パッケージ名][6]オプションを付ける
(Vala/Genieのプログラムから実行ファイルを生成)
$ valac (オプション...) -o [出力ファイル] [ソースファイル...]

(C言語のソースファイルを生成)
$ valac -C (オプション...) [ソースファイル...]

簡単なコード例

[任意]ファイル名:hello.vala ライセンス:パブリックドメイン
/*
 * Valaを用いた簡単なコード例
 * valac --pkg posix -o hello hello.vala
 */

using Posix;      // Posix名前空間内の関数やクラスを名前空間指定なしで用いる

namespace TestNS  // 名前空間を定義(複数段階可)
{
  class Foo       // クラスを定義
  {
  }
}

namespace Hello
{
  class TestClass
  {
    static int cnt = 0;  // 静的メンバ
    public
    TestClass (string str)
    {
      cnt++;
      GLib.stdout.printf ("TestClass(): cnt=%d str=%s\n", cnt, str);
    }
    public int
    get_count ()
    {
      return cnt;
    }
    public static int
    max (int a, int b)
    {
      return (a < b) ? b : a;
    }
  }
  class MainClass
  {
    public static int
    main (string[] args)
    {
      /* 標準出力に文字列を出力 */
      GLib.stdout.printf ("Hello, Work!!\n");

      /* 引数の数によって分岐 */
      if (args.length > 3)  // 「.length」で長さが得られる
      {
        GLib.stderr.printf ("Too many arguments\n");

        return EXIT_FAILURE;
      }

      /* 個別の要素を処理する */
      foreach (string arg in args)
      {
        GLib.stdout.printf ("arg:" + arg + "\n");
      }

      /* new演算子を用いてオブジェクトを生成する */
      TestNS.Foo foo = new TestNS.Foo ();  // 後で使用していないので警告が出る
      TestClass tst1 = new TestClass ("aaa");
      TestClass tst2 = new TestClass ("bbb");
      var tst3 = new TestClass ("ccc");  // 「var」キーワード

      /* メンバ関数を呼び出す */
      GLib.stdout.printf ("tst1.get_count(): %d\n", tst1.get_count ());
      GLib.stdout.printf ("tst2.get_count(): %d\n", tst2.get_count ());
      GLib.stdout.printf ("tst3.get_count(): %d\n", tst3.get_count ());
      GLib.stdout.printf ("TestClass.max(3, 5): %d\n", TestClass.max (3, 5));
      GLib.stdout.printf ("TestClass.max(6, 6): %d\n" , TestClass.max (6, 6));
      GLib.stdout.printf ("TestClass.max(15, 7): %d\n", TestClass.max (15, 7));

      return EXIT_SUCCESS;
    }
  }
}
実行例
(引数を付けずに実行)
$ ./hello
Hello, Work!!
arg:./hello
TestClass(): cnt=1 str=aaa
TestClass(): cnt=2 str=bbb
TestClass(): cnt=3 str=ccc
tst1.get_count(): 3
tst2.get_count(): 3
tst3.get_count(): 3
TestClass.max(3, 5): 5
TestClass.max(6, 6): 6
TestClass.max(15, 7): 15

(引数を2つ付けて実行)
$ ./hello abc xyz
Hello, Work!!
arg:./hello
arg:abc
arg:xyz
TestClass(): cnt=1 str=aaa
...

(引数を3つ付けて実行)
$ ./hello abc xyz 123
Hello, Work!!
Too many arguments
使用したバージョン:
  • Vala 0.26.2, 0.28.0
[1]: GLibという汎用ライブラリの一部で、GTK+などで使用されている
[2]: コンパイラはVala言語で実装されており、ビルドする場合は既に出力済みのC言語のソースをCコンパイラで処理する形になる
[3]: C++言語専用のライブラリは使用できないが、内部実装にC++が使用されているものでもライブラリとしてC言語から使用できるものなら可
[4]: 基本的にGNOME系が多く、一部のその他ライブラリのものも含まれる
[5]: 例として、GLib.stdoutPosix.stdoutは “using Posix;” を記述すると単に “stdout” とは書けなくなる
[6]: パッケージ名は.vapiファイルのファイル名に対応しており、例としてGTK+ではgtk+-3.0.vapiなので--pkg gtk+-3.0となる