インスタンス変数
まずはこのプログラムを考えましょう。
class FibonacciCaltulator
def initialize
@cache = [0, 1]
end
def calc(n)
cache = @cache[n]
return cache if cache
result = calc(n - 1) + calc(n - 2)
@cache[n] = result
return result
end
end
calculator = FibonacciCaltulator.new
p calculator.calc(50) # => 12586269025
フィボナッチ数を再帰で求めるプログラムですが、そのまま再帰すると計算量が爆発するのでその都度計算結果を@cacheに入れています。
こうすることで、50番目のフィボナッチ数でもほぼ一瞬で求められます。
クロージャ
さて、これをTypeScriptで書いてみましょう。
const fibonacciCalculator = () => {
let cache = [0, 1];
const calc = (n: number): number => {
const cacheValue: number | undefined = cache[n];
if (cacheValue !== undefined) {
return cacheValue;
}
else {
const result = calc(n - 1) + calc(n - 2);
cache[n] = result;
return result;
}
};
return calc;
};
const calculator = fibonacciCalculator();
console.log(calculator(50)); // => 12586269025
コードの見た目はRubyとほぼ同じですが、cacheの扱いに違いがあります。
このプログラムでは、calculatorはfibonacciCalculatorの返り値であり、その定義の外側にあるcacheを参照しています。
fibonacciCalculatorを呼び出したときに、cacheの情報がcalcの中に「閉じ込められた」状態で、calculatorに代入されます。
calculatorを呼び出すと、中に「閉じ込められた」cacheを使用して、キャッシュが次々に更新されます。
この「閉じ込められた」変数は、このcalculator専用のものとなり、他の場所からは呼び出せません(この閉じ込めをクロージャといいます)。
類似点
インスタンス変数とクロージャの中の変数は見た目は異なるものですが、類似点もあります。
- インスタンス変数はインスタンスごとに個別に保持される。クロージャの中の変数はそのクロージャを作るごとに個別に保持される
- インスタンス変数はゲッターとセッターにより直接操作できなくすることができる。クロージャの中の変数はプロパティの関数により直接操作できなくすることができる
- オブジェクト(ハッシュ)がクロージャを持つ場合に起こります
- インスタンス変数は破壊的に変更されうる。クロージャの中の変数も破壊的に変更されうる
実際、上のTypeScriptでもcacheが「fibonacciCalculatorクラスのインスタンスcalculatorのインスタンス変数」のように機能していますね。
結論
インスタンス変数はクロージャである。