読者です 読者をやめる 読者になる 読者になる

foreachでインデックスが取得できる喜び

D言語

昔ながらのfor文が煩わしい場合があるというのはよく言われることである。for文を使いたいよくあるシチュエーションとして、配列等のデータ構造の要素に順にアクセスしたい場合が挙げられる。この場合、後でデータ構造を変更する度に要素へのアクセス方法が変わるのは面倒だ。それでなくても、例えば配列にアクセスするときにa[i]のようにインデックスを指定して要素を取り出すこと自体煩わしいことだろう。

そこで、Dにはforeach文というものが存在する。foreach文自体はそんなに珍しいものではなく、似たようなものはそこかしこの言語で実装されている。pythonのfor文などはDのforeach文の動作に近い。

しかし、私はいわゆるforeach文的な構文を使うときに、どうにも我慢ならないことがある。そう、インデックスを取得するのが面倒なのだ。foreachとは少し違うが、C++イテレータをfor文で回してコンテナの要素にアクセスするときにも同様の問題を抱えたことがある。foreach文の中でインデックスにアクセスしたいことは多々あり、これは極めて深刻な問題だ。もし愚直に解決しようとすると、例えば以下のようになるだろう。

auto a = new int[10];  // 要素数10の配列を宣言
int i = 0;  // ここでインデックス用の変数を定義
foreach(var; a){
    /* Do something with var and i. */
    i++;
}

なんっっって気持ち悪いんだ!foreachの構文とは全く関係ない変数が、ユーザが勝手に決めたなんとも脆いセマンティクスの上でインデックスとして動作している。動作してはいるが、foreach文の中でうっかりiを書き換えたりしたら速攻でバグが発生してしまうし、そもそも構文的に全く美しくない。

C++イテレータを使うともう少しマシにはなる。

vector<int> a(10);  // 要素数10のvectorを宣言
for(vector<int>::iterator i = a.begin(); i != a.end(); i++){
  // distance()関数でイテレータ間の距離が分かる
  cout << distance(a.begin(), i) << endl;
}

これでも問題はないのだが、いかんせん長い。一行があまりにも長くなると混乱を生じやすくなる。それにより、私はこれまで幾度となくバグを産んできた。

しかし、私は幸運なことにDに出会った。Dではこの問題を以下のようにエレガントな構文を導入することで解決している。

auto a = new in[10];
// 最初の変数がインデックス、二番目の変数が配列の要素を表す
foreach(i, val; a){
  writefln("%s: %s", i, val);
}

これを実行すると以下のようになる。

0: 0
1: 0
2: 0
3: 0
4: 0
5: 0
6: 0
7: 0
8: 0
9: 0

美しい。これ以上言うことはない。

余談だが、Dのforeachを使うと、配列の要素を書き換えることも可能である。それには以下のように変数の前にrefを付ける。

foreach(ref val; a){
  val = 10;  // 配列のすべての要素に10を代入する
}

さらに余談だが、Dでは上と等価な処理を、わずか1行で書くことも可能である。

a[] = 10;  // まさに黒魔術

foreach文が持つ本質的な美しさに加えて、Dではインデックスへのアクセス方法も提供している。おまけに、要素をその場で変更することも可能だ。Dのforeach文を知ってしまったら、きっと誰しも他の言語を使うのが少し面倒に感じることだろう。

参考:プログラミング言語D

プログラミング言語D

プログラミング言語D