eachを回した時に、何もせずエラーにもならず、そのまま処理が続いたせいでハマったので備忘録に。
Rangeのインスタンス生成
始点<終点の場合、始点>終点の場合どちらでも問題なくインスタンス生成はできる。
aaa = Range.new(1,5)
=> 1..5
bbb = 1..5
=> 1..5
ccc = Range.new(5,1)
=> 5..1
ddd = 5..1
=> 5..1
Range#each
Range#eachの挙動
問題となるのは、逆順で生成したRangeでeachを回した場合。
aaa.each {|i| puts i}
1
2
3
4
5
=> 1..5
ccc.each {|i|puts i}
=> 5..1
Range#eachの説明
るりまによるとRange#eachはsuccでイテレーションしているらしい。
https://docs.ruby-lang.org/ja/latest/method/Range/i/each.html
Integerのsuccの説明を見てみると単に次の整数を返すだけのようだ。
aaa.begin.class
=> Integer
aaa.begin
=> 1
aaa.begin.succ
=> 2
ccc.begin
=> 5
ccc.begin.succ
=> 6
Range#eachの実装
おそらくstatic VALUE range_each(VALUE range)が呼ばれ、その先でwhileの条件を満たさないので1周もループせず処理が終わっている模様。
static VALUE
range_each(VALUE range)
{
//...省略
if (!NIL_P(end))
range_each_func(range, each_i, 0);
else
for (;; beg = rb_funcallv(beg, id_succ, 0, 0))
rb_yield(beg);
//...省略
}
static void
range_each_func(VALUE range, int (*func)(VALUE, VALUE), VALUE arg)
{
int c;
VALUE b = RANGE_BEG(range);
VALUE e = RANGE_END(range);
VALUE v = b;
if (EXCL(range)) {
while (r_less(v, e) < 0) {
if ((*func)(v, arg)) break;
v = rb_funcallv(v, id_succ, 0, 0);
}
}
else {
while ((c = r_less(v, e)) <= 0) {
if ((*func)(v, arg)) break;
if (!c) break;
v = rb_funcallv(v, id_succ, 0, 0);
}
}
}
/* compares _a_ and _b_ and returns:
* < 0: a < b
* = 0: a = b
* > 0: a > b or non-comparable
*/
static int
r_less(VALUE a, VALUE b)
{
VALUE r = rb_funcall(a, id_cmp, 1, b);
if (NIL_P(r))
return INT_MAX;
return rb_cmpint(r, a, b);
}