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 を定義させた方がいんでないかい?