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

D言語を勉強したいなら、標準ライブラリであるPhobosを読めとよく言われています。 D言語の機能の殆どは、Phobos内で使われていると言っても過言ではないでしょう。 そんなわけで、std.functionalモジュールのソースコードを読んでいきます。

今回は、 std.functional.unaryFunを見てみます。 この2つがどんな感じに使われるのかというと、



import std.functional;
import std.stdio;

void main(){
    unaryFun!"a * a" (4).writeln(); // 16
    unaryFun!(a => (a.writeln, 0)) (16) // 16
    binaryFun!"a < b"(6, 7).writeln(); // true     binaryFun!((a, b) => writeln(a, " ",  b))(6, 7) // 6 7
}

という感じです。 上記の例を見てもらうとわかると思うのですが、unaryFunやbinaryFunは文字列やラムダ式、関数リテラル、デリゲートリテラルを元に新たに関数を生成しています。 この2つの唯一の違いは、 生成される関数の引数の数です。 さて、あらかた使い方もわかったので、早速読んでみましょう。 まずは、unaryFunから行きます。



template unaryFun(alias fun, bool byRef = false, string parmName = "a")
{
    static if (is(typeof(fun) : string))
    {
        // 省略
    }
    else
    {
        alias fun unaryFun;
    }
}

大まかな実装を見るために、外枠だけを抜き出しました。 この外枠を見ると、unaryFunは、

  1. funに文字列が渡された時には、なんか色々やって、関数を返す。
  2. funになにか実行できる物、ラムダ式や関数リテラル、デリゲートリテラルが渡されたときは、単純にそれのエイリアスを返す。

という処理を行なっていることがわかります。 特に2の処理内容から、下記のように書けることがわかります。



import std.stdio;

void main(){
    unaryFun!((a, b, c, d) => a + b + c + d))(1, 2, 3, 4).writeln // 10
}

このサンプルコードの肝は、unaryFunなのに生成されている関数の引数が4つになっているというところです。 これの原因は、渡された実行可能なものの引数の数が一つであるかどうかチェックしてないこと。 といっても、これがバグかどうかは疑問が残るところです。

なにはともあれ、1の処理内容を見てみましょう。



template unaryFun(alias fun, bool byRef = false, string parmName = "a")
{
    static if (is(typeof(fun) : string))
    {
        template Body(ElementType)
        {
            enum testAsExpression = "{ ElementType "~parmName
                ~"; return ("~fun~");}()";
            static if (__traits(compiles, mixin(testAsExpression)))
            {
                enum string code = "return (" ~ fun ~ ");";
                alias typeof(mixin(testAsExpression)) ReturnType;
            }
            else
            {
                static assert(false, "Bad unary function: " ~ fun ~
                        " for type " ~ ElementType.stringof);
            }
        }

        static if (byRef)
        {
            Body!(ElementType).ReturnType unaryFun(ElementType)(ref ElementType __a)
            {
                mixin("alias __a "~parmName~";");
                mixin(Body!(ElementType).code);
            }
        }
        else
        {
            Body!(ElementType).ReturnType unaryFun(ElementType)(ElementType __a)
            {
                mixin("alias __a "~parmName~";");
                mixin(Body!(ElementType).code);
            }
        }
    }
    else
    {
        alias fun unaryFun;
    }
}

3行目のis(typeof(fun) : string)で、funが文字列かどうか判定しています。 3行目のstatic ifの内部で、Bodyという名前のtemplateが定義されています。 Bodyは、

  1. テスト用に、ElementTypeとparmNameとfunを使って、funを実行するデリゲートリテラルの文字列表現を作る。
  2. mixinと__traitsのcompilesで、上で作った文字列のコンパイルが通るかどうかチェックする。
  3. コンパイルが通ったら、非テスト用のfunを実行するコードの文字列表現と、funの返値型を返す。
  4. コンパイルが通らなかったら、エラーを吐く。

という処理を行なっています。 そして、unaryFunという名前の関数の中で、parmNameへのエイリアスと、Bodyが返す文字列をmixinしています。 unaryFunの返値型は、Bodyが返す型になっています。 isRefによって、引数をrefにするかどうか分ける処理も挟まれています。

このように、D言語特有の機能である__traitsのcompilesやmixinを駆使して、unaryFunは文字列を元に関数を生成しています。 とても面白いですね。