a == b || a == c || a == d || a == eのような同じ値に対して何度も同値比較をするコードを簡潔に書く方法を学んだので日記を兼ねて書く。

いきさつ

あれは今から6日……いや、8日前の出来事だったか。 まあいい、このブログにおいては多分……4日後の出来事だ。 自分はstatic foreachを使ったFizzBuzzを書いていた。

D言語を書くときはフォーマッターであるDfmtを定期的に走らせるのが癖になっているのだが、static foreachが正しく整形されないことに気がついた。 以下のようにカッコのインデントが過剰に深くなってしまうのだ。

static foreach (/* ... */)
        {
        /* ... */
    }

この時は手で整形することで対処したが、少しDfmtのコードを読んでみようと思った。

OSSのソースを読むことは以前から定期的に試みては挫折していた。 しかしD言語の性質か、Dfmtが特別きれいなコードを書いているのか、割と簡単に理解することができ、また自分にも修正できそうだと思った。 どうやらstatic foreachはまだサポートされていないようだったので、そのためのコードを書いてプルリクエストを送った。

Add support for static foreach by kotet · Pull Request #304 · dlang-community/dfmt

すると反応があった。

実例

最初自分は以下のようなコードを書いた。 arr[i + 1]に対して何度も同値比較をしてORでつないでいる。

if (arr[i] == tok!"static" && (arr[i + 1] == tok!"if"
    || arr[i + 1] == tok!"else" || arr[i + 1] == tok!"foreach"
    || arr[i + 1] == tok!"foreach_reverse") && (i + 2 >= index || arr[i + 2] != tok!"{"))
// 後略

それに対してこのようなレビューをもらった。

I would import std.algorithm : among and make this

arr[i + 1].among!(tok!"if", tok!"else", tok!"foreach", tok!"foreach_reverse")

because it has a lot of cases now 1

ここまでケースが増えた場合はamongを使うといい、ということらしい。

std.algorithm.comparison.among

std.algorithm.comparison.amongは複数の引数を取り、第1引数と等しい物があった場合は1から始まるインデックスを返す。 等しい物がなかった場合0を返す。

DはCと同じように0false、それ以外はtrueとみなされる。 そのため

among(arr[i + 1], tok!"if", tok!"else", tok!"foreach", tok!"foreach_reverse")

(arr[i + 1] == tok!"if"
    || arr[i + 1] == tok!"else" || arr[i + 1] == tok!"foreach"
    || arr[i + 1] == tok!"foreach_reverse")

と同じ効果をもつのだ。 数値がbool扱いされる仕様はバグの元になったりするので、 以上の仕組みを理解したうえで使うようにしたほうが良いと思う。

さらにこの関数にはテンプレート版があり、比較する値たちがコンパイル時に決まっている場合以下のようにすると最適化してくれる。

among!(tok!"if", tok!"else", tok!"foreach", tok!"foreach_reverse")(arr[i + 1])

そこにUFCSとカッコの省略を組み合わせると最初のコードになる。

arr[i + 1].among!(tok!"if", tok!"else", tok!"foreach", tok!"foreach_reverse")

感想

たしか自作でないソフトウェアに自分の書いたDのコードがマージされるのは初めてのことである。 なんだかようやく自分がプログラマであるという保証が得られたようで嬉しい。

OSSに参加するというのは全く予想していなかった知識が得られて楽しいなと思った。 たぶん一人ではamongの存在になかなか気づかなかっただろうし、仮に見つけたとしても数値とboolの関係を思い出してこのように使えるものだと思うことはなかっただろう。

std.algorithm.comparisonにあるということはひょっとして初めからこの目的で作られた関数だったんだろうか?