LoginSignup
0
0

More than 5 years have passed since last update.

逆順のRangeでeachを実行した場合の挙動

Last updated at Posted at 2018-12-03

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);
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0