Tuple で遊ぶ

Ver-0.174 が出て、嬉々として遊んでいる人多いだろうな、と思いながらこれを書いている。

さて Tuple である。
std.typetuple には

template TypeTuple(TList...)
{
    alias TList TypeTuple;
}

というテンプレートがあるが、これは引数を型か値か特定していないので、値にも使える。

import std.typetuple;
import std.stdio;
import house.string;

void main() {
	alias TypeTuple!(int, char[], double) Types;
	writefln(typeid(Types));

	alias TypeTuple!(1, "AAA", 3.14) Values;
	writefln(typeid(typeof(Values)));
	writefln(toString(Values));
}
...
(int,char[],double)
(int,char[3],double)
1, AAA, 3.14

まぁ、製作者の意図としては、型タプルの製造に使って欲しいんだろうから、値のタプルを作るときには、

alias TypeTuple ValueTuple;

とでもやっておきべきなんでしょね。

あ、あと Tuple をいきなり toString() で文字列化しているがこれは、この日記でさんざん書いてきた汎用文字列化関数テンプレート使ってます。std.string とのバッティングがやで、メソッド名を頭大文字にしてきたが、このほど慣例にならって、toString() にしました。
まぁぶつかったときには、

alias house.string.toString stringOf;

とでもするとして。
あと、モジュール名も、house.tostring から 単なる house.string にしました。

さて、Tuple の初期化をばまずやってみるか。

	Types values = Values;
	writefln("values = %s", toString(values));
...
values = 0, , nan

だめどすな。

	Types v1 = (1, "AAA", 3.14);
	Types v2 = [1, "AAA", 3.14];
	Types v3 = TypeList!(1, "AAA", 3.14);

	writefln(toString(v1));
	writefln(toString(v2));
	writefln(toString(v3));
...
0, , nan
0, , nan
0, , nan

どれもコンパイルは通るが、いずれも意図した動作にならない。

ただ、変数宣言のあと、動的に代入するのはできた。

	Types values;
	values[0] = Values[0];
	values[1] = Values[1];
	values[2] = Values[2];
	writefln("values = %s", toString(values));
...
values = 1, AAA, 3.14

これは連想配列リテラルがないので、

	int[char[]] map;
	map["A"] = 0;
	map["B"] = 1;
	map["C"] = 2;

とやらねばならなかったのと、事情が似ている。
まぁ、そんなわけでレシピ集に書いた Map.Entry 作ったわけだが。
でも実質タプルリテラルは定義できてるのになぁ。

初期化がだめなら代入である。
なんとか1ステートメントで代入できないかと、以下のテンプレートを作成。

template CopyTuple(Values...) {
  void to(Types...)(inout Types lhs) {
    lhs[0] = Values[0];
    static if (Values.length > 1)
      CopyTuple!(Values[1..$]).to(lhs[1..$]);
  }
}

ちょっと複雑だが、これは2重のテンプレートね。
外側の CopyTuple を値でインスタンス化し、内側の to を型でインスタンス化する。
使い方は、

	CopyTuple!(1, "AAA", 3.14).to(values);

といった具合。
しかし、

tuple.d(45): tuple Values is used as a type
tuple.d(45): Error: can only slice tuple types, not void

なんでよ。
Values は値としてインスタンス化したはずなんすがねぇ。
ナニがご不満?

再帰がだめなので、羅列型にに逃げた。

template CopyTuple(Values...) {
  void to(Types...)(inout Types lhs) {
    static if (Values.length > 0) lhs[0] = Values[0];
    static if (Values.length > 1) lhs[1] = Values[1];
    static if (Values.length > 2) lhs[2] = Values[2];
    static if (Values.length > 3) lhs[3] = Values[3];
    static if (Values.length > 4) lhs[4] = Values[4];
    static if (Values.length > 5) lhs[5] = Values[5];
    static if (Values.length > 6) lhs[6] = Values[6];
    static if (Values.length > 7) lhs[7] = Values[7];
    static if (Values.length > 8) lhs[8] = Values[8];
    static if (Values.length > 9) lhs[9] = Values[9];
  }
}

みっともねぇなぁ。つくづく羅列型はナサケない。(誰かできる人教えて下さい)
ともあれ、目的達成である。

	Types values;
	CopyTuple!(1, "AAA", 3.14).to(values);
	writefln("values = %s", toString(values));
...
values = 1, AAA, 3.14

ところで Tuple を利用しようとしてすぐに思いつくのが構造体。
0.174 から構造体に tupleof プロパティがついたので。
toString() を型汎用、Tuple 対応に作ってあるので、構造体の toString() なんか以下のですんでしまう。

  struct S {
    int id = 10;
    char[] name = "NAME";
    char[] toString() {
      return "{ " ~ house.string.toString(this.tupleof) ~ " }";
    }
  }
  writefln(S.init);
...
{ 10, NAME }

これは構造体のメンバ数、型、メンバ名に依存していないので、テンプレート化すべき。

template StructToString(S) {
  static assert(is (S == struct));
  char[] toString() {
    return "{ " ~ house.string.toString(this.tupleof) ~ " }";
  }
}
...
  struct S {
    int id = 10;
    char[] name = "NAME";
    mixin StructToString!(S);
  }
  writefln(S.init);
...
{ 10, NAME }

さらに、構造体の初期化はレシピ集の「構造体のコンストラクタ風」にさっきの CopyTuple が使える。

  struct S {
    int id = 10;
    char[] name = "NAME";
    mixin StructToString!(S);
    static S opCall(typeof(S.init.tupleof) args) {
      S s;
      CopyTuple!(args).to(s.tupleof);
      return s;
    }
  }
  writefln(S(3, "initialized"));
...
{ 3, initialized }

これまたテンプレート化すべき。

template StructInitializer(S) {
  static assert(is (S == struct));
  static S opCall(typeof(S.init.tupleof) args) {
    S s;
    CopyTuple!(args).to(s.tupleof);
    return s;
  }
}
  struct S {
    int id = 10;
    char[] name = "NAME";
    mixin StructToString!(S);
    mixin StructInitializer!(S);
  }
  writefln(S(3, "initialized"));
...
{ 3, initialized }

もっともこれは、D言語が構造体は静的な初期化しかゆるしていないので、こんなことしてるわけだが。
配列も昔はそーだったけど、なんで静的な初期化しかできんのかねぇ?

ともあれ、メンバがどうだろうと構造体は初期化、文字列化が可能になってしまった。

念のため、いろいろ試してみた。

  mixin Map!(char[], int);
  struct S {
    int id;
    char[] name;
    double age;
    creal[] points;
    int[char[]] map;
    mixin StructToString!(S);
    mixin StructInitializer!(S);
  }
  S s = S(
    10,
    "Struct",
    24.7,
    [ 0.0+1.2i, 1.1+2.3i, 2.2+3.4i ],
    Map(Entry("AAA", 1), Entry("BBB", 2), Entry("CCC", 3))
  );
  writefln(s);
...
{ 10, Struct, 24.7, [0+1.2i,1.1+2.3i,2.2+3.4i], [(AAA:1),(BBB:2),(CCC:3)] }

だいじょぶでおますな。

クラスの Tuple に関してはまた今度。