インクリメントについて

2chRuby スレッドを見てみたら、a++ がなぜないかで盛り上がってました。定期的に出る話題。

ぼくも 1.8 では後置 ++ を使いたい時が結構ありました。例えばこんな感じ。

# ["foo", "bar", "baz"] を [[0, "foo"], [1, "bar"], [2, "baz"]] にする
i = 0
ary = ary.map do |v|
  t = [i, v]
  i += 1
  t
end

やっぱりこう書きたい!

i = 0
ary = ary.map {|v| [i++, v] }

悔しくてこう書いてみるものの、すぐ考え直して最初の例に落ち着く。

i = -1
ary = ary.map {|v| [i += 1, v] }

場合によっては以下も書いてたかも。

ary2 = []
ary.each_with_index {|i, v| ary2 << [i, v] }
ary = ary2

まあでも、++ って往々にしてトリッキーなコードを産むので、ない方がいいかもしれないですね。ちなみに 1.9 では Enumerator#with_index のおかげで、こういうシチュエーションはかなり減りました。

ary = ary.map.with_index {|v, i| [i, v] }


それはそれとして、a++ を (tmp = a; a = tmp.succ; tmp) の syntax sugar にすればいいというのは (前から言われてると思うけど) 、個人的にはそれなりに説得力を感じます。ちょっと実装してみました。

$ ./ruby -e 'a = 0; p a++; p a++; p a'
0
1
2

以下、フィーリングで書いたパッチ。いかにもバグありそう。前置 ++ は reduce/reduce conflict になったので対応してない。あと -- のためには、まず Fixnum#pred か何かを導入するところからはじめないといけないんだよね。String#pred がうまく定義できないということで、却下されたことがあった気がする。
追記: 1.9 には Integer#pred がすでにありました。ありがとうございます > znz さん

Index: parse.y
===================================================================
--- parse.y	(revision 19747)
+++ parse.y	(working copy)
@@ -703,6 +703,7 @@
 %token tCOLON2		/* :: */
 %token tCOLON3		/* :: at EXPR_BEG */
 %token <id> tOP_ASGN	/* +=, -=  etc. */
+%token tINC		/* ++ */
 %token tASSOC		/* => */
 %token tLPAREN		/* ( */
 %token tLPAREN_ARG	/* ( */
@@ -1819,6 +1820,34 @@
 			$$ = dispatch2(assign, $1, dispatch2(rescue_mod, $3, $5));
 		    %*/
 		    }
+		| var_lhs tINC
+		    {
+		    /*%%%*/
+			/*  (t = a; a = t.succ; t) */
+			ID id = internal_id(), vid = $1->nd_vid;
+			NODE *tidw, *tidr;
+			if (dyna_in_block()) {
+			    dyna_var(id);
+			    tidw = NEW_DASGN_CURR(id, 0);
+			    tidr = NEW_DVAR(id);
+			}
+			else {
+			    local_var(id);
+			    tidw = NEW_LASGN(id, 0);
+			    tidr = NEW_LVAR(id);
+			}
+			$$ = block_append(
+			    node_assign(tidw, gettable(vid)),
+			    block_append(
+				node_assign(
+				    assignable(vid, 0),
+				    NEW_CALL(tidr, rb_intern("succ"), 0)),
+				tidr));
+			fixpos($$, $1);
+		    /*%
+			$$ = dispatch3(opassign, $1, '+', $1);
+		    %*/
+		    }
 		| var_lhs tOP_ASGN arg
 		    {
 		    /*%%%*/
@@ -6764,6 +6793,10 @@
 	    pushback(c);
 	    return '+';
 	}
+	if (c == '+') {
+	    lex_state = EXPR_BEG;
+	    return tINC;
+	}
 	if (c == '=') {
             set_yylval_id('+');
 	    lex_state = EXPR_BEG;