試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

Vala言語におけるクロージャについて

Vala 0.7.6からは、関数内で定義される匿名(ラムダ)関数が外側の関数の変数を参照できるようになっていて、クロージャと呼ばれるものの用途で使えるようになっている。
バージョン0.7.5まででは、匿名関数内で外側の関数の変数を参照するコードをコンパイルしようとすると、中間形式のC言語のコードには変換されるものの、

[名前].vala.c: In function ‘_lambda0_’:
[名前].vala.c:xx: error: expected expression before ‘/’ token
[名前].vala.c:xx: error: ‘[変数名]’ undeclared (first use in this function)
[名前].vala.c:xx: error: (Each undeclared identifier is reported only once
[名前].vala.c:xx: error: for each function it appears in.)
[名前].vala.c: In function ‘_lambda1_’:
[名前].vala.c:xx: error: ‘[変数名]’ undeclared (first use in this function)
error: cc exited with status 256
Compilation failed: 1 error(s), 0 warning(s)

のように変数が未定義と扱われていた。

使用例

using GLib;

namespace ClosureTest
{
  public delegate void IncrementFunc ();  // 匿名関数の戻り値なし
  public delegate int IncrementFunc2 ();  //           戻り値int
  public delegate int PlusFunc (int x);
  public static IncrementFunc counter ()
  {
    int i = 0;
    /* 「クロージャ」はreturnの後ろの匿名関数部分(丸括弧の開始から「}」まで) */
    return () =>
    {  // ここから匿名関数の処理で、この関数の外にある変数iにアクセスしている */
      i++;  // 2ずつ増やすなら「i += 2;」
      print ("i: %d\n", i);
      // return文なし
    };
  }
  public static IncrementFunc2 counter2 ()
  {
    int i = 0;
    return () =>
    {
      i++;
      return i;
    };
  }
  public static PlusFunc pluser ()
  {
    int i = 0;
    return (x) =>
    {
      i += x;
      return i;
    };
  }
  class MainClass
  {
    public static void main (string[] args)
    {
      IncrementFunc c;
      IncrementFunc2 c2;
      PlusFunc a;
      print ("IncrementFunc c = counter();\n");
      c = counter ();
      print ("c(); -> "); c ();     // 1
      print ("c(); -> "); c ();     // 2
      print ("c(); -> "); c ();     // 3
      print ("c(); -> "); c ();     // 4
      print ("c(); -> "); c ();     // 5
      print ("IncrementFunc2 c2 = counter2();\n");
      c2 = counter2 ();
      print ("c2(): %d\n", c2 ());  // 1
      print ("c2(): %d\n", c2 ());  // 2
      print ("c2(): %d\n", c2 ());  // 3
      print ("c2(): %d\n", c2 ());  // 4
      print ("c2(): %d\n", c2 ());  // 5
      print ("PlusFunc a = pluser();\n");
      a = pluser ();
      print ("a(1): %d\n", a (1));  //  1 (+1)
      print ("a(2): %d\n", a (2));  //  3 (+2)
      print ("a(3): %d\n", a (3));  //  6 (+3)
      print ("a(7): %d\n", a (7));  // 13 (+7)
      print ("a(9): %d\n", a (9));  // 22 (+9)
    }
  }
}

下は実行例。cとc2は繰り返し呼び出すとカウントが1ずつ増えていくが、前者は関数の中で表示を行い、後者では匿名関数の戻り値をmain()側で表示している。最後のaは引数の値を内部に保持している値に加えた合計が戻り値になっている。

IncrementFunc c = counter();
c(); -> i: 1
c(); -> i: 2
c(); -> i: 3
c(); -> i: 4
c(); -> i: 5
IncrementFunc2 c2 = counter2();
c2(): 1
c2(): 2
c2(): 3
c2(): 4
c2(): 5
PlusFunc a = pluser();
a(1): 1
a(2): 3
a(3): 6
a(7): 13
a(9): 22

(2009/9/19)コード中の一部の英単語の用法が不適切だったのを修正し、実行例の出力もそれに合わせて修正

注意点

仕様により、クラスのコンストラクタ内でのクロージャの記述はできない。*1しかし、クロージャの記述を含む別の関数をコンストラクタから呼ぶことはできる。

using GLib;
using Gtk;

/*
 * コンストラクタ内でのクロージャの記述をテスト(コンパイルエラーになる):
 *  valac -D TEST --pkg gtk+-2.0 -o closuretest2 closuretest2.vala
 * コンパイルエラーにならない書き方:
 *  valac --pkg gtk+-2.0 -o closuretest2 closuretest2.vala
 */

namespace ClosureTest2
{
  class MainWindow : Gtk.Window
  {
    Gtk.Button button;
    public MainWindow ()
    {
      this.button = new Gtk.Button.with_mnemonic ("_Hello");
      this.add (this.button);
      this.destroy += Gtk.main_quit;
#if TEST
      /*
       * 仕様によりコンストラクタ内のクロージャの記述は不可
       * (error: Closures are not supported in GObject-style creation methods)
       */
      int a = 111;
      this.button.clicked += () =>
      {
        print ("a:%d\n", a);
      };
#else
      /* クロージャを含む別の関数をコンストラクタから呼ぶことは可能 */
       this.init ();
#endif
       this.set_size_request (160, 100);
     }
#if ! TEST
    void init ()
    {
      int b = 222;
      this.button.clicked += () =>  // 右辺がクロージャ
      {
        print ("b:%d\n", b);  // 外側のb(222)を参照
        Gtk.main_quit ();
      };
    }
#endif
  }
  class MainClass
  {
    public static void main (string[] args)
    {
      Gtk.init (ref args);
      MainWindow win = new MainWindow ();
      win.show_all ();
      Gtk.main ();
    }
  }
}

また、Vala言語特有の文法として、コンストラクタよりも先に実行されるconstructブロックというものがあるのだが、この中であれば、クロージャが記述できる。ただし、コンストラクタと異なり、引数を受け取って処理することはできない。

using GLib;
using Gtk;

/*
 * valac --pkg gtk+-2.0 -o closuretest3 closuretest3.vala
 */

namespace ClosureTest3
{
  class MainWindow : Gtk.Window
  {
    Gtk.Button button;
    /*
     * constructブロック
     * (クラス名のコンストラクタよりも先に呼ばれるが *引数を処理できない* )
     * http://live.gnome.org/Vala/Tutorial#head-8e8f20629800e8d7a4957d5e8fda99ab51c734b9
     * の中ではクロージャが記述できる
     */
    construct
    {
      print ("construct block\n");
      this.button = new Gtk.Button.with_mnemonic ("_Hello");
      this.add (this.button);
      this.destroy += Gtk.main_quit;
      int a = 111;
      this.button.clicked += () =>
      {
        print ("a:%d\n", a);
        Gtk.main_quit ();
      };
      this.set_size_request (160, 100);
    }
    public MainWindow ()
    {
      print ("MainWindow()\n");  // 「construct block」の文字より後に出力される
    }
  }
  class MainClass
  {
    public static void main (string[] args)
    {
      Gtk.init (ref args);
      MainWindow win = new MainWindow ();
      win.show_all ();
      Gtk.main ();
    }
  }
}

関連記事:

関連URL:

使用したバージョン:

  • Vala 0.7.6

*1:error: Closures are not supported in GObject-style creation methods