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 に関してはまた今度。

Tuple恐るべし

Ver-0.174 がリリースされた。

それに伴い、11/11 のコードで

	writefln(TA.length);
	writefln(typeid(TA));
	writefln(typeid(TA[0]));
	writefln(typeid(TA[1]));
	writefln(typeid(TA[2]));
...
3
TypeInfo
TypeInfo[0]
TypeInfo[1]
TypeInfo[2]

だったのが、

3
(int,double,char[3])
int
double
char[3]

と表示されるようになった。

今回の更新はすごい。

TypeTuple が超強力である。
これのおかげで過去に書いていた TypeArray が不要になった。
TypeTuple が TypeArray そのものである。

	alias TypeTuple!(int, double, char[]) Types3;
	writefln(typeid(Types3));
	writefln(Types3.length);
	writefln(typeid(Types3[0]));
	writefln(typeid(Types3[1]));
	writefln(typeid(Types3[2]));
...
(int,double,char[])
3
int
double
char[]

しかもこれ、ネストさせることによって連結もできる。

	alias TypeTuple!(int, double, char[]) IDS;
	alias TypeTuple!(short, float, creal) SFC;
	alias TypeTuple!(IDS, SFC) Many;
	writefln(typeid(Many));
	writefln(Many.length);
	writefln(typeid(Many[0]));
	writefln(typeid(Many[1]));
	writefln(typeid(Many[2]));
	writefln(typeid(Many[3]));
	writefln(typeid(Many[4]));
	writefln(typeid(Many[5]));
...
(int,double,char[],short,float,creal)
6
int
double
char[]
short
float
creal

おまけにスライシングさえ使える。

	writefln(typeid(Many[0..3]));
	writefln(typeid(Many[3..6]));
...
(int,double,char[])
(short,float,creal)

型汎用の functor はたったこれだけですんでしまう。

interface Functor(R, Args...) {
	alias R ReturnType;
	alias Args ArgsType;
	ReturnType opCall(ArgsType args);
}

これだけで、全ての functor の派生元を定義している。
引数の個数を1個または2個に限定している STL など目ではない。

std.typetuple には、TypeTuple に対するさまざまなユーティリティが定義されているが、Erase があるのに、EraseAt がない。
が、スライシング構文を使えばなんとかなった。

template EraseAt(int n, TList...) {
	alias TypeTuple!(TList[0..n], TList[n+1..length]) EraseAt;
}

	alias EraseAt!(2, Many) Few;
	writefln(typeid(Few));
...
(int,double,short,float,creal)

型タプルの表現力には目を見張るものがある。
DTL はかなりの部分が書き換えられるだろう。

bind1st でも bind2nd でもない bindNthも実装できそうである。

しかもまぁ、std.traits は追加されるは、クラスと構造体に tupleof プロパティが追加されるはで、汎用プログラミングパラダイスの状況である。(しかし、typename キーワードが欲しい……)

可変個型引数あれこれ

昨日のちょっと修正。

char[] ToString(T1, T2, TA...)(T1 value1, T2 value2, TA values) {
	const char[] delim = ", ";
	char[] buffer = ToString(value1) ~ delim ~ ToString(value2);
	foreach (v; values) {
		buffer ~= delim;
		buffer ~= ToString(v);
	}
	return buffer;
}

あとですねぇ。
可変個型引数を使って型コンテナを実装しようとしたんだが、どーもウマくいかん。

class TypeArray(TA...) {
	const int length = TA.length;
	template typeAt(int n) {
		alias TA[n] typeAt;
	}
}

これでできればサイコーである。シンプルで美しい(自賛)。
だがそーはいかない。

	alias TypeArray!(int, char, double) Types3;
	writefln(typeid(Types3));
	writefln(Types3.length);
	writefln(typeid(Types3.typeAt!(0)));
	writefln(typeid(Types3.typeAt!(1)));
	writefln(typeid(Types3.typeAt!(2)));
...
TypeArray
3
TypeInfo[0]
TypeInfo[1]
TypeInfo[2]

こんな具合。

つまり、ここで言う TA ってのは型の配列ではなく、TypeInfo の配列なのね。欲しいのは型情報ではなく型だ。

納得いかんのでもそっと試してみた。

void testTypes(TA...)(TA values) {
	foreach (v; values) {
		writefln(typeid(typeof(v)));
	}
	writefln(values.length);
	writefln(typeid(typeof(values[0])));
	writefln(typeid(typeof(values[1])));
	writefln(typeid(typeof(values[2])));
}

void main() {
	testTypes(1, 3.14, "ABC");
}
...
int
double
char[3]
3
int
double
char[3]

よかですね。このように型引数ではなく、引数の typeof を取るとウマくいく。
但し、

	writefln(typeid(typeof(values)));

とやると、

Assertion failure: '0' on line 4601 in file 'mtype.c'

となる。

では、型引数の方にアクセスしてみるとどうか?

	writefln(TA.length);
	writefln(typeid(TA));
	writefln(typeid(TA[0]));
	writefln(typeid(TA[1]));
	writefln(typeid(TA[2]));
...
3
TypeInfo
TypeInfo[0]
TypeInfo[1]
TypeInfo[2]

ダメですな。
引数から型を取ることはできるが、型引数からはとれない。

関数テンプレートの場合、型引数だけでなくホントの引数も取るので、関数内で型情報を取れる。
しかし

	alias TypeArray!(int, char, double) Types3;

てった具合に型だけからインスタンス化した場合、テンプレート内で可変個型引数を使って型を取得できない。

可変個型引数は便利だけど、今んとこ威力を発揮すんの関数テンプレートだけだなぁ。
やはり、羅列型で書いたナサケナな TypeArray を再帰を使って書くぐらいが関の山のようである。

まとめて配列

しばらく更新しない間にD言語の方が更新されていた。
遅延評価はなにやらすごそうだけど、価値が判断できん。(Haskell知らないので)
配列リテラルと、可変個テンプレート型引数が嬉しい。

で、こんなのを作ってみた。

char[] ToString(T1, T2, TA...)(T1 value1, T2 value2, TA values) {
	const char[] delim = ", ";
	char[] buffer = ToString(value1) ~ delim ~ ToString(value2) ~ delim;
	foreach (v; values) {
		buffer ~= ToString(v);
		buffer ~= delim;
	}
	if (buffer.length > delim.length) buffer = buffer[0..length-delim.length];
	return buffer;
}

で、実験。

{
	mixin Map!(char[], int);
	ToString(
		'A',
		0,
		3.14,
		2.71 + 3.2i,
		[1, 2, 3],
		[1.1, 2.2, 3.3],
		Map(
			Entry("AAA", 1),
			Entry("BBB", 2),
			Entry("CCC", 3)
		)
	).writefln();
	ToString(1).writefln();
	ToString(1, 2, 3).writefln();
	[1, 2, 3].ToString().writefln();
}
...
A, 0, 3.14, 2.71+3.2i, [1,2,3], [1.1,2.2,3.3], [(AAA:1),(BBB:2),(CCC:3)]
1
1, 2, 3
[1,2,3]

お〜、でけてるでけてる。

しっかし、型の違うものを配列につっこんで、foreach でぐるぐる回せるってぇのはすっごいな。Tuple もなぁんも定義してないのに。

配列の共有

配列をラッピングして共有するためのクラスを作った。
なんでこんなものを作ったかと言うと、配列の共有のされ方が
ハンパだからである。

    char[] ar1 = "ABC";
    char[] ar2 = ar1;           // 配列を代入
    assert(ar1.ptr == ar2.ptr); // この時点で2つの配列は同じもの
    
    ar1[0] = 'a';               // 配列要素をいぢくる
    ar2[2] = 'c';
    assert(ar2[0] == 'a');      // 共有されてるので
    assert(ar1[2] == 'c');      // もう一方も変わってる
    
    ar1 ~= 'Z';                 // 配列に要素を追加
    assert(ar1.ptr != ar2.ptr); // もはや共有されていない

    ar1[0] = 'A';               // 値を変えても
    ar2[2] = 'C';
    assert(ar2[0] == 'a');      // もう一方の要素は
    assert(ar1[2] == 'c');      // もとのまま

といった具合。

Copy On Write とはそーいったものかも知れないが、やはり直感に反していてキモい。共有していると思っていたものがあるときパッと2つのものになるちうのは。

こうした挙動を避けるためには、最初から dup を使って2重化しておけばよい

    ar2 = ar1.dup;

では、要素が追加されたり削除されても共有したままでいたい場合には?
クラスで包んであげるしかない。

というわけで作ってみたが、まだ不完全。
toHash と opCmp の実装がショボいので、基本型以外の要素を持つものは、連想配列のキーに使えない。
この SharedArray にありえる全ての型を内部要素に持たせようとすると、型汎用の toHash と opCmp が必要。(以前、汎用文字列化関数を作ったみたいな感じで)
まだメンドくさくてやっていない。
土日の間作るかどうかは未定。

続きを読む

STLとDTL


なんか、id:shinichiro_hさんがトラバってくれてるので、STLの functional のポーティングしたのとか。


ちなみに id:shinichiro_h がせっかく教えてくれた mem_fun はまだ未実装m(__)m。
型コンテナ使った、汎用ファンクタも未実装。


ポーティングの際には、STL と DTL の両方を見ながらやったけど、若干ポリシーに違いがあるよう。


STL の functional は、ファンクタの基底クラスに unary_function, binary_function と命名してしまっており、型引数の順番も戻り値型が最後に来ているが、DTLの方は、

template Function(R, A0) { class Function
...
template Function(R, A0, A1) { class Function

ちゅうふうに、テンプレート型引数の数でディスパッチし、戻り値型が先頭に来ている。
また、

template Function(R, A0, A1, A2) { class Function

といった、引数3つのファンクタも定義してる。


どういうことかっちうと、STL は、ファンクタは引数が2つまでのものしかサポートしてへんけど、DTL の方は可変個型引数に対する色気が見える。


というわけなので、id:shinichiro_h さんがポーティングしてくれた Loki の Typelist とか、TypeArray の コードを DTL 開発者に送ってあげれば、それが採用されて、可変個型引数のファンクタを DTL に入れてくれるかも……


ところで、関数オブジェクトだけ定義しても、それを渡す先のアルゴリズムが、ないとしょーがないわけですが、 algorithm のポーティングも部分的にしてないわけでゃないのすが、膨大すぎて、ねぇ。

続きを読む

InExpression


Ver 0.160 がリリースされた。
更新履歴を読む。

Added operator overloading of InExpression.

ほー、in 式 のオーバーロードがサポートされたんだ。
連想配列をラッピングしたクラス定義が完璧にできるな。


さっそくお試し。

import std.stdio;
import std.string;
import house.tostring;

class Map(K, V) {
	private V[K] map;
	this() {}
	
	K[] keys() { return map.keys; }
	V[] values() { return map.values; }
	
	char[] toString() { return map.ToString(); }

	V opIndex(K key) { return map[key]; }
	V opIndexAssign(V value, K key) { return map[key] = value; }
	V* opIn(K key) { return (key in map); }
}

void main() {
	alias Map!(char[], int) CIMap;
	CIMap map = new CIMap();
	map["AAA"] = 1;
	map["BBB"] = 2;
	map["CCC"] = 3;
	writefln(*("BBB" in map));
}
...
C:\d\test>dmd -run map
map.d(25): rvalue of in expression must be an associative array, not map.Map!(ch
ar[],int).Map
map.d(25): can only * a pointer, not a 'int'

なんで?

in 式の多重定義サポートしたんでないの?

	writefln(*map.opIn("BBB"));
...
2

これはさすがに通るな。

	writefln(*map.in("BBB"));

これはだめ。

	writefln(*(map in "BBB"));

なんとこれが通ってしまった。
え〜、連想配列連想配列もどきクラスで in 式の順序が逆になんの〜。


in ではなくて includes または contains って感じだな〜。


これじゃ opIn のオーバーロードを禁止して、opIn_r を定義させた方がいんでないかい?