「func() の帰り値が 1 または 2 または 3 のとき」みたいなことを書きたいことはよくあります。
r = func() if r == 1 || r == 2 || r == 3 p "hit" end
しかし、いちいち変数に代入しないといけないのが気に入りません。とくに変数名を考えるのが面倒くさい。あと、r == を何回も書くのも気に食わない。
そこで、Array#include? を使う人もいます。
if [1, 2, 3].include?(func()) p "hit" end
しかしこれは、1 、2 、3 、func() の語順が直感に合いません。この処理の主体は func() の帰り値なので、最初に func() を書きたいのです。「ary が x を含むかどうか」と「x が ary に含まれるかどうか」は意味は同じですが、ニュアンスが違うのです。*1 *2 *3
そこで、しばしば Object#in? みたいなメソッドが提案されます。*4
if func().in?([1, 2, 3]) p "hit" end
けれど、この提案は Ruby core に採用されていません。その理由を推察すると:
- in? は別に Object の性質ではないので、Object のメソッドとして変 (哲学的な理由)
- ちょっとした表記程度の問題では Object の名前空間を汚すほど価値が感じられない (実用性の観点)
- DSL 好き Rails 界隈の人が考えそうな提案がなんとなく気に食わない (天邪鬼なコミッタ)
まあ、どの理由もそれなりに納得できます。とくに最後とか。
と、ここまでが背景。
そこで今回は、x in a, b, c という文法を追加するパッチを書いてみました。
if func() in 1, 2, 3 p "hit" end
うわあ……。
あと、splat もかけます。
ary = [2, 3] if func() in 1, *ary p "hit" end
Object#in? に比べて、in 中置演算子にはさらに以下の利点があります。
ちなみに、in はすでにキーワードなので、新たにキーワードが増えるわけではありません。というわけで、どうでしょうね。
個人的には、case で良いかなという気がしてきたのでどうでもよくなりました *6 。
case func() when 1, 2, 3 p "hit" end
まあ、elsif では case が使えないという問題はあるのですが。
in はせっかくのキーワードなのに全然活用されていないので、もっと面白い意味の構文とか考えるといいかもですね、という結論。一応、以下パッチ。
diff --git a/parse.y b/parse.y index 78e66ee..a4152e6 100644 --- a/parse.y +++ b/parse.y @@ -747,6 +747,7 @@ static void token_info_pop(struct parser_params*, const char *token); %nonassoc modifier_if modifier_unless modifier_while modifier_until %left keyword_or keyword_and %right keyword_not +%nonassoc keyword_in %nonassoc keyword_defined %right '=' tOP_ASGN %left modifier_rescue @@ -1205,6 +1206,14 @@ expr : command_call $$ = dispatch2(unary, ripper_intern("not"), $3); %*/ } + | expr keyword_in args + { + /*%%%*/ + $$ = NEW_CALL($3, rb_intern("include?"), NEW_LIST($1)); + /*% + $$ = dispatch3(binary, $1, ripper_intern("in"), $3); + %*/ + } | '!' command_call { /*%%%*/
*1:天泣記 (2009-05-31) では「英語で思考するプログラマ」特有の直感みたいに言われているけれど、日本語でも同じだと思う。
*2:いわば、1 == x でなく x == 1 と書きたいのと同じだと思う。C++ のコーディングスタイルで、「x == 1 ではなく 1 == x と書け」 (== を = と打ち間違えた時のための安全策) とかたまに聞くけれど、頭おかしいと思う。 こう言わないと x == 1 と書いてしまう人が多いという証拠だろう。
*3:当然、include? は長すぎるという思いもある。
*4:[ruby-list:3647] 、[ruby-core:23543] 、Ruby Facets など。
*5:作らないように実装できます。が、添付のパッチでは実装をさぼっているので作ります。
*6:以前卜部さんに「case 使え」と言われたとき、「when 節が 1 つしかない case は気持ち悪い」と答えたのだけれど、case と when を同じ行に書いてみたらありかなという気がしてしまった。