D言語の標準ライブラリを読む read!(std.functional, 2)

前回に引き続き、今回もPhobosのstd.functionalを読んでいきます。 今回は、 std.functional.memoizeを読みます。

std.functional.memoizeはその名前の通り、任意の関数をメモ化することができます。 まずはじめに、簡単な例を見て行きましょう。


import std.functional, std.stdio;

int factImpl(int i){
    "*".write();
    return i == 0 ? 1 : i * fact(i - 1);
}

alias memoize!factImpl fact;

void main(){
    10.fact().writeln(); // ***********3628800
    10.fact().writeln(); // 3628800
    11.fact().writeln(); // *39916800
}

関数factImplをメモ化して、関数factを生成しています。とても簡単に関数をメモ化できていますね。

それでは、memoizeのソースコードを見てみましょう。


template memoize(alias fun, uint maxSize = uint.max)
{
    ReturnType!fun memoize(ParameterTypeTuple!fun args)
    {
        // 中略
    }
}

外枠を見ると、memoizeはテンプレートで、引数としてfunとmaxSizeを取るようです。 テンプレート内部でmemoizeという関数が定義されています。 関数memoizeの引数型は、funの引数型のタプルで、返値型はfunの返値型になっています。 memoizeは、funと同じように使える関数ならなければならないので、返値型や引数型は当然こうなっています。

次に、関数memoizeの内部を見てみましょう。


static ReturnType!fun[Tuple!(typeof(args))] memo; // (1)
auto t = tuple(args);
auto p = t in memo;
if (p) return *p; // (2)
static if (maxSize != uint.max) // (3)
{
    if (memo.length >= maxSize) memo = null;
}
auto r = fun(args); 
memo[t] = r; // (4)
return r;

(1)で関数の結果を格納する連想配列、memoを宣言しています。 memoのキーの型は、引数のTupleになっています。これによって、一気に関数funの結果をmemoに保存することができます。Tuple便利ですね。 ちなみに、memoはstaticなので、関数が終了してもmemoは残り続けます。

(2)あたりで、memoに渡された引数をキーとした値があるかどうかチェックし、あったらその値を返しています。

(3)では、memoの最大数を設定した場合の処理を行なっています。

(4)あたりで、funを実際に実行、その結果をmemoに保存し値を返しています。

このように、std.functional.memoizeは、staticな連想配列に値を保存しておくことによって、渡された関数をメモ化しています。 とても面白いですね。