D言語で分割代入がしたかった
TL;DR
- テンプレートよくわからない。助けて。
本編
1行で入力を取りたい
競プロでしばしばこんな文面を見ますよね:
入力は以下の形式で与えられる。
X Y Z
X, Y, Z
が整数だったとして、これを例えばpythonで受け取りたければ1行でこう書けます:
X, Y, Z = map(int, input().split())
ところがD言語には分割代入の仕組みがないので、通常こう書くしかありません (追記: readf
という便利な関数があるのを教えていただきました。これで十分でしたね……。) :
int X, Y, Z; auto line = readln.chomp.split.to!(int[]); X = line[0]; Y = line[1]; Z = line[2];
長いのはもちろんのこと、今後一生使わない変数line
がスコープを汚すのも気に食わない感じがします。
そんなわけで、もう少しすっきりさせたいね、と思ったので簡単なmixinを書きました。
import std.algorithm; import std.conv; import std.range; import std.stdio; import std.string; // 「read words as T into D」 のつもり string readAsInto(T, string D)() { return T.stringof ~ " " ~ D ~ ";" ~ "foreach(tup; zip(" ~ "[" ~ (D.split(",").map!(s => "&" ~ s.strip).join(",")) ~ "]," ~ "readln.chomp.split.to!(" ~ T.stringof ~ "[])" ~ ")) *tup[0] = tup[1];"; }
これを使うと先の入力はこのように受け取れます:
mixin(readAsInto!(int, "X, Y, Z"));
短くてめでたい。
分割代入したかった
ところで、さっきのmixinは標準入力を受け取る用途に特化しすぎています。 より一般的に分割代入ができるようにmixinを書き直してみたのですがあまりうまくいかず。 多分テンプレート分かってない。 以下は迷走の記録です。
mixin()
に放り込む文字列はコンパイル時に確定していて、かつ合法なソースコードでなければなりません。したがって、文字列の生成は関数ではなくテンプレートによって行う必要があります。
ここで必要になるテンプレートは「変数名の列と値の列を受け取って、各変数を宣言し、各値を代入する」ようなソースコードの文字列を生成するものですので、シグネチャは(気持ち的には)多分こんな感じです:
string decomposeInto(InputRange!E r, string D)() { // ... }
当然ですが型変数E
が未定義なので上のコードは非合法です。どうしよう。
1. alias を使う
そういえば library reference でテンプレート引数をalias
にしているのを見かけたことがあります。Language reference 読んでも正直意味がよく分かりませんでしたが、まあ手を動かせば何か分かるかも知れんしと思い、ものは試しで以下のように書きました:
string decomposeInto(alias R, string D)() { return (ElementType!(typeof(R))).stringof ~ " " ~ D ~ ";" ~ "foreach(tup; zip(" ~ "[" ~ (D.split(",").map!(s => "&" ~ s.strip).join(",")) ~ "]," ~ R.stringof ~ ")) *tup[0] = tup[1];"; }
これを次のようにして使います:
void main() { auto a = readln.chomp.split.to!(int[]); mixin(decomposeInto!(a, "X, Y, Z")); X.writeln; }
> rdmd test.d 1 2 3 1
動くじゃん。
しかし!main()
を以下のように書き直すと……
void main() { // 変数 a を省略しただけ mixin(decomposeInto!(readln.chomp.split.to!(int[]), "X, Y, Z")); X.writeln; }
> rdmd test.d test.d(9): Error: identifier 'chomp' of 'readln.chomp.split.to!(int[])' is not defined test.d(10): Error: undefined identifier 'X'
という謎エラーで怒られます。なんで……?
2. 型変数 E を定義する
やっぱりよく分かっていないものを使うとうまくいきません。分かる範囲でどうにかしましょう。
要はE
が定義できればよかろうということで次のように書きました:
template decomposeInto(E) { string decomposeInto(InputRange!E R, string D)() { // ... } }
冗長ですが仕方がないので以下のようにして呼びます:
auto a = readln.chomp.split.to!(int[]); mixin(decomposeInto!int.decomposeInto!(a, "X, Y, Z"));
すると:
> rdmd test.d test.d(11): Error: cannot resolve type for decomposeInto!int test.d(12): Error: undefined identifier 'X'
詳しいことを何も教えてくれないこの謎のエラーは、テンプレート名と関数名を別にすることでなぜか解消します*1。しかし今度は
>rdmd test.d test.d(11): Error: variable a cannot be read at compile time test.d(11): while looking for match for hoge!(a, "X, Y, Z") test.d(12): Error: undefined identifier 'X'
と怒られます。いや確かに考えてみればそれはそう。でもじゃあなんでalias
なテンプレート引数は実行時に渡しても怒られないんだ……?というかalias
って何?
D言語くんと対話できない
別にどうしても分割代入を書きたくて苦しんでいるわけではなく、吐かれたエラーを理解できないところで苦しんでいます。D言語くんと真に仲良くなれる日は訪れるのでしょうか。
おわりに
こんな本質から外れたことばっかやってるから水色になれないんだぞ。
*1:本当になんで?