Luaリファレンス 要注意点 ~クロージャ~

  • クロージャとは
    クロージャを知らない人にとってクロージャは理解しずらいものである。 しかし、実は、クラスの類似品と考えてよい。

    例えば、
    -- C#的な何かメタな言語で書かれたカウンタークラス
    class Counter {
        int iCurCnt; // 現在のカウント値
    
       // 生成時に初期値をコピー
        Counter(int iDefaultCnt) {
            iCurCnt= iDefaultCnt;
        }
    
        // 指定された値をカウントに増加。指定値がなければ、1増加
        IncCounter(int iIncValue = 1) {
            iCurCnt += iIncValue;
        }
    }
    
    といったクラスがあるとします。
    「メソッドが生成時用メソッド(コンストラクタ)」と、他1つ(増加用メソッド)しかありません。
    このような「生成時用」と「他1つ」といった限定シチュエーションの場合、 クロージャを使えば簡易版クラスが作成出来る、といった程度のことです。
    -- luaのクロージャで書いた版。
    function CreateCounter(iDefaultCnt)
        local iCurCnt = iDefaultCnt -- 生成時に値をとっておく。関数内のみに定義してあるので隠ぺいも出来ている。
    
        return function(iIncValue) -- 関数そのものを返す。
            iIncValue = iIncValue or 1
            iCurCnt = iCurCnt + iIncValue
            return iCurCnt    
        end
    end
    
    local IncCounter = CreateCounter(5)
    
    print(IncCounter())
    print(IncCounter(5))
    print(IncCounter(2))
    

    クロージャには、クラスとは別の「そのローカル変数の隠蔽性」に着目した応用方法もありますが、
    最初はこのように理解しておきましょう。

    クロージャ自体は:

    「ネストされた関数(=A)」を、「スコープ外から参照するいずこかに存在する変数のライフサイクル」が生存する限りにおいては、
    「該当のネストされた関数(=A)が参照するローカル変数」もまた、「元来のスコープを超えて生存する」、そのような機構

即ち、上の例でいうと、
CreateCounter() で一度 function オブジェクトが返ります。

クロージャにより、IncCounter が、この関数オブジェクトを参照している間は、local iCurCnt も消滅しないようになるのです。

即ち、Aのライフサイクルが生存しているので、Aが参照しているローカル変数(iCurCnt)も 元来のスコープを超えて生存し続けるわけです。

クロージャの基本

  • 簡易クラスとして
    function newCounter ()
        local i = 0
        return function ()
                   i = i + 1
                   return i
               end
    end
    
    c1 = newCounter()
    print(c1())  ---> 1
    print(c1())  ---> 2
    
  • クロージャと隠蔽
    -- math.sin の関数を置き換えたい。しかしそれに付随する変数は全て隠ぺいしたい場合
    do
       local oldSin = math.sin
       local k = math.pi/180
       math.sin = function(x)
                      return oldSin(x*k)
                  end
    end
    

イタレータとクロージャ

  • python 他と同じ。for in が対応していることも含めて標準的)
    function values (t)
        local i=0
        return function ()
            i=i+1
            return t[i]
        end
    end
    
    t = {10,20,30}
    
    for element in values(t) do
        print(element)
    end
    
    for element in values(t) do
        print(element)
    end
    
    このように2回繰り返しても、ちゃんとfor in 単位でクロージャがリセットされるので期待通りの挙動となる。

    以下のようにvtを用意してしまうと、当然変数寿命が維持されてしまい、上手く動かなくなるので注意
    t = {10,20,30}
    vt = values(t)
    
    for element in vt do
        print(element)
    end
    
    for element in vt do
        print(element)
    end
    

トップ   差分 履歴 リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2014-09-29 (月) 00:00:00