クロージャが使われているコードを調べてみる。

クロージャについては自分でも理解出来ているとは言い難く、誰かに説明することも出来ないので、クロージャのコードを書いたり紹介してみたいと思います。
Perlでいうと、アルパカ本でも少し紹介されていましたのでそちらを見るとわかりやすいかもしれません。

クロージャとは?
  • Wikipediaの概要には下記のように書かれています。

典型的には、クロージャはある関数全体が他の関数(以下、エンクロージャ)の内部で宣言されたときに発生し、内部の関数はエンクロージャのローカル変数(レキシカル変数)を参照する。実行時に外部の関数が実行された際、クロージャが形成される。クロージャは内部の関数のコードとエンクロージャのスコープ内の必要なすべての変数への参照からなる。
クロージャはプログラム内で環境を共有するための仕組みである。レキシカル変数はグローバルな名前空間を占有しないという点でグローバル変数とは異なっている。またオブジェクトのインスタンス変数とは、オブジェクトのインスタンスではなく関数の呼び出しに束縛されているという点で異なる。
クロージャ関数型言語では遅延評価やカプセル化のために、また高階関数の引数として広く用いられる。

よくあるPerlでのクロージャの例

  • 上記では、Wikipediaの説明にある「クロージャは内部の関数のコードとエンクロージャのスコープ内の必要なすべての変数への参照」の「エンクロージャ」がmake_closure関数であり、「内部の関数のコード」はreturnで宣言されている$cntをインクリメントしている関数であり、「全ての変数」は$cntを指しています。
  • そしてこの$cntはそれぞれclosure1とclosure2で別々に保持されていることがわかります。
  • この$cntが別々に保持されていることがクロージャの大きな特徴かなと思います。
JavaScriptでは

JavaScriptでは、イベント処理などをはじめコールバック関数を渡すときなどによくクロージャが使われていますが、クロージャを使うことで下記のように本当なプライベートなメンバ変数を作ることができたりもします。

  • 上記の例では、getName,setName以外からはnameにアクセスすることが出来ず、まさにカプセル化が行われています。
    • Javaのプライベート変数でも同じことが出来そうですが、Javaのプライベート変数はクラス内からであれば直接アクセスすることが出来ます。
      • したがって、例えば必ず偶数の数値を設定しておきたいような変数があっても、誰かがそのクラスにメソッドを追加することで奇数の値をセットされてしまう恐れがあります。その点上記のようにクロージャで作った関数の場合は、絶対にメソッド経由になるのでセッターでチェックをしておけば奇数を設定することが出来なくなります。
      • (まぁ、そこまで厳密にカプセル化を行う必要があるかは置いておいて。。)
CPANモジュールからクロージャを学ぶ
  • やはり実践的なコードからも学びたいということで、「Class::Data::Inheritable 」のコードを取り上げてみたいと思います。


上記の問いかけに対して、@hide_o_55さんがクロージャの教材としてこのモジュールを紹介してくださりました。ありがとうございます!

  • というわけでこのモジュール唯一の関数であるmk_classdataのソースを。(日本語での説明を追加しています)
sub mk_classdata {
    my ($declaredclass, $attribute, $data) = @_; 
    # $declaredclassはクラス名、$attributeはクラスデータ属性名、$dataはクラスデータ。

    # インスタンス(リファレンス)から呼ばれたら例外を投げる。クラス関数を定義するため。
    if( ref $declaredclass ) {
        require Carp;
        Carp::croak("mk_classdata() is a class method, not an object method");
    }

    # クラス関数を作成する。
    # クロージャを作成している。対象の変数は$attribute、$declaredclass、$data
    my $accessor = sub {
        my $wantclass = ref($_[0]) || $_[0];

        # 呼ばれたクラスが作成した時のクラスと異なる場合はクラス関数を作り直す。(オーバーライド)
        # そのまま値をセットするとスーパークラスのクラスデータを上書きしてしまうため。
        return $wantclass->mk_classdata($attribute)->(@_)
          if @_>1 && $wantclass ne $declaredclass;

        # セットする値があるときはセットしてその値を返す
        $data = $_[1] if @_>1;
        return $data;
    };

    # クラス関数を登録する。
    my $alias = "_${attribute}_accessor";
    *{$declaredclass.'::'.$attribute} = $accessor;
    *{$declaredclass.'::'.$alias}     = $accessor;
}
  • 使ってみる


  • このモジュールは、クラスデータを作成するためのモジュールで、クラスが継承された時のためにクロージャが使われています。短いソースですが本当に勉強になります。
おまけ
  • 自分でもクロージャを使った何かわかりやすい例はないかなとずっと考えていて、こんなコードを考えてみました。


う〜ん、何かいい例えやサンプルがあれば追加します!