doctree
doctree copied to clipboard
両端が数値リテラルの範囲式が評価ごとに違うオブジェクトを返すことがある
範囲オブジェクト には,
範囲式はその両端が数値リテラルであれば、何度評価されても同じオブジェク トを返します。
とあります。
実験した範囲では,両端が Integer だと確かにそうでしたが,Float や Rational ではそうなりませんでした。
3.times.map{ 1.0..2.0 }.map(&:object_id).uniq
# => [65553920, 65553900, 65553840]
3.times.map{ 2r..3r }.map(&:object_id).uniq
# => [69043780, 69043760, 69043740]
当該の記述は間違いと考えてよさそうなので,
範囲式はその両端が数値リテラルであれば、何度評価されても同じオブジェクトを返します。そうでなければ評価されるたびに新しい範囲オブジェクトを生成します。
の二文は単純削除でいいでしょうか?
両端が整数リテラルであれば何度評価されても同じオブジェクトが返るので、「数値リテラル」を「整数リテラル」に置き換えても良いのではないでしょうか。
ちなみに、Bignumであっても同じオブジェクトが返るのかが気になって調べましたが2.4からは同じオブジェクトが返るようです。
$ docker run -it --rm -v /tmp/test.rb:$(pwd)/test.rb rubylang/all-ruby env ALL_RUBY_SINCE=ruby-1.8.0 ./all-ruby -e 'def x() 1..20000000000000000000000000000000000000000000000000000000000000000000000000; end; p x.object_id == x.object_id'
ruby-1.8.0 false
...
ruby-2.4.0-preview2 false
ruby-2.4.0-preview3 true
...
ruby-2.7.0 true
2.3のドキュメントはすでに生成していないと思うので、「Fixnumでは〜」のような説明は書かなくても良さそうです。
また、begin-less / end-less rangeの場合は同じオブジェクトは返らないようになっています。
$ docker run -it --rm -v /tmp/test.rb:$(pwd)/test.rb rubylang/all-ruby env ALL_RUBY_SINCE=ruby-2.6 ./all-ruby -e 'def x() 1..; end; p x.object_id == x.object_id'
ruby-2.6.0 false
...
ruby-2.7.0 false
$ docker run -it --rm -v /tmp/test.rb:$(pwd)/test.rb rubylang/all-ruby env ALL_RUBY_SINCE=ruby-2.7 ./all-ruby -e 'def x() ..1; end; p x.object_id == x.object_id'
ruby-2.7.0 false
これについては書かなくても良いかなと思います。
では第一文の「数値リテラル」を「整数リテラル」(もしくは Integer リテラル)に変え,第二文は削除,としましょうか? 第二文を残すなら,同じオブジェクトを返すのが「両端が Integer」のケース以外に無いということを確認する必要があると思います。
第二文を残すなら,同じオブジェクトを返すのが「両端が Integer」のケース以外に無いということを確認する必要があると思います。
確認したところ、両端がIntegerのケース以外は最適化されないようでした。
https://github.com/ruby/ruby/blob/c47cd4be28840159251b4c66df71e10e979316a0/compile.c#L8311-L8332
case NODE_DOT2:
case NODE_DOT3:{
int excl = type == NODE_DOT3;
VALUE flag = INT2FIX(excl);
const NODE *b = node->nd_beg;
const NODE *e = node->nd_end;
if (number_literal_p(b) && number_literal_p(e)) {
if (!popped) {
VALUE val = rb_range_new(b->nd_lit, e->nd_lit, excl);
ADD_INSN1(ret, line, putobject, val);
RB_OBJ_WRITTEN(iseq, Qundef, val);
}
}
else {
CHECK(COMPILE_(ret, "min", b, popped));
CHECK(COMPILE_(ret, "max", e, popped));
if (!popped) {
ADD_INSN1(ret, line, newrange, flag);
}
}
break;
}
なお、number_literal_p
は実際にはIntegerかどうかを見ているようです。
またpopped
は値が評価されない場合の最適化なので、今回の件には関係なさそうですね。 ref: https://github.com/ruby/ruby/commit/451e0a6ee1362b4cfc504087f0d3232bbaeb76ca
提示していただいた C のコードが読めないのですが,結論としては「数値リテラル」を「整数リテラル」に変えるのみ(第二文は削除しない),でいいでしょうか?
若干気になるのが,これを仕様としてリファレンスに書いちゃっていいのかどうか,です。今たまたまそういう仕様だけど,将来変わったりしないのか,とか。 将来変わったらリリースノートに載るはず(?)だから気にしなくていいのかな・・・?