前回のエントリで std.variant の変遷(?)についてなんとなく追えました。
今回は実際に触ってみて、理解を深めようと思います。
$ dmd --version
DMD64 D Compiler v2.091.1
Copyright (C) 1999-2020 by The D Language Foundation, All Rights Reserved written by Walter Bright
Variant
Variant型には色んな値を入れることができます。TypescriptのAnyみたいなやつです。
import std.variant;
import std.stdio;
void main() {
Variant a;
a = "hello";
writeln(a, " ", a.type, " ", a.get!(string));
a = 3.14;
writeln(a, " ", a.type, " ", a.get!(double));
a = 100;
writeln(a, " ", a.type, " ", a.get!(int));
a = [1, 2, 3];
writeln(a, " ", a.type, " ", a.get!(int[]));
a = ["a":"A", "b":"B", "c":"C"];
writeln(a, " ", a.type, " ", a.get!(string[string]));
}
$ rdmd main.d
hello immutable(char)[] hello
3.14 double 3.14
100 int 100
[1, 2, 3] int[] [1, 2, 3]
["c":"C", "a":"A", "b":"B"] immutable(char)[][immutable(char)[]] ["c":"C", "a":"A", "b":"B"]
type() で型を確認でき、 get(T)() で値を取得できます。
getができなかった場合、VariantExceptionが発生します。
import std.variant;
import std.stdio;
void main() {
Variant b;
writeln(b.hasValue, " ", b.type);
b = 1000;
writeln(b.hasValue, " ", b.type);
b = null;
writeln(b.hasValue, " ", b.type);
b = Variant();
writeln(b.hasValue, " ", b.type);
}
$ rdmd main.d
false void
true int
true typeof(null)
false void
hasValue()は値を持つか検査します。初期化直後はfalse、値を設定後はtrueになります。
nullを入れてもtrueなのですね。
import std.variant;
import std.stdio;
void main() {
Variant c = 10;
c += 5;
writeln(c, " ", c.type);
c += 0.1;
writeln(c, " ", c.type);
c = c.coerce!(string) ~ "a";
writeln(c, " ", c.type);
c = 123.45f;
writeln(c.type, " ", c.convertsTo!(int), " ", c.convertsTo!(double));
}
$ rdmd main.d
15 int
15.1 double
15.1a immutable(char)[]
float false true
Variantは演算することもできます。明示的に変換する場合、coerce(T)()を使います。
convertsTo(T)()で変換可能か検査することができます。
Algebraic
Algebraicは型の種類を指定できるVariantです。
たとえば auto a = Algebraic!(int, double)(0) は int または double のみが入る型です。
Typescriptだと a: int | double = 0 みたいに書いたりしますよね。たしか。
import std.variant;
import std.stdio;
alias Number = Algebraic!(int, double);
void main() {
Number d = 1;
writeln(d, " ", d.type, " ", d.get!(int));
d -= 0.00001;
writeln(d, " ", d.type, " ", d.get!(double));
// d = "5";
// stringはコンパイルエラーになる
}
$ rdmd main.d
1 int
0.99999 double
上記のようにaliasで別名をつけたりすると分かりやすいかもしれません。
Variant同様に演算などもできますが、指定外の値を入れようとするとコンパイルエラーになります。
import std.variant;
import std.stdio;
alias Any = Algebraic!(long, double, string, bool, typeof(null));
void main() {
Any[] any = [ Any("a"), Any(3.14), Any(true), Any(10_000_000L), Any(null) ];
foreach (Any a; any) {
auto ret = a.visit!(
(long v) => "A",
(double v) => "B",
(string v) => "C",
(_) => "other" // その他の型を1つにまとめられる
);
writeln(a, " ", a.type, " ", ret);
}
}
$ rdmd main.d
a immutable(char)[] C
3.14 double B
true bool other
10000000 long A
null typeof(null) other
visit(T)(handlers...)は型にマッチする処理を行い、結果を返します。
Algebraicに指定してある型を網羅してないとコンパイルエラーになります。
(特定の型のみ処理したい場合は、最後に型を指定しないlambda式を書くと良いです。いずれにもマッチしない型の場合にはこれが処理されます。)
また、網羅してなくてもコンパイルエラーにならないtryVisit(T)(handlers...)もあります。(※ただし、実際に網羅外の値が来た場合にはVariantExceptionが発生します。)
なんとなく便利そうなこのvisitなのですが、Variantには使えずAlgebraicでないとダメみたいです。残念。