in 中置演算子

「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 を同じ行に書いてみたらありかなという気がしてしまった。