RHGがある

またしても範囲オブジェクトの話。知り合いに教えてもらった驚愕の仕様。

irb(main):005:0> (1..2).include?(1.5)
=> true

include?もしくはmember?は、与えられた引数がRangeオブジェクトのメンバであればtrueを返すメソッド。(1..2)は配列に変換すると、[1,2]となるので、Rangeオブジェクトというのは結局、「連続した要素を持つ配列を定義しやすいようにしたオブジェクト」だと思っていたけど、この理解は不十分らしい。

range.cのドキュメント部には、こう書かれている。

/*
 *  call-seq:
 *     rng.member?(val)  =>  true or false
 *     rng.include?(val) =>  true or false
 *  
 *  Returns <code>true</code> if <i>obj</i> is an element of
 *  <i>rng</i>, <code>false</code> otherwise.  If beg and end are
 *  numeric, comparison is done according magnitude of values.
 *     
 *     ("a".."z").include?("g")  # => true
 *     ("a".."z").include?("A")  # => false
 */

begもendも数値なら、大小関係にしたがって判定するという。ぼくは整数のことばかり考えていたけど、確かに「範囲」というのだから、これは合理的だ。しかし、明示的に(1.0 .. 2.0)とかしたときにだけ実数の範囲ということにしてもいいような気がする。うーん、こういうデザインの背後にはどういう思想があるのだろうか。

実際にrange.cで数値として比較している部分を眺めてみた。

static VALUE
range_include(VALUE range, VALUE val)
{
    VALUE beg = RANGE_BEG(range);
    VALUE end = RANGE_END(range);
    int nv = FIXNUM_P(beg) || FIXNUM_P(end) ||
             rb_obj_is_kind_of(beg, rb_cNumeric) ||
             rb_obj_is_kind_of(end, rb_cNumeric);

    if (nv ||
        !NIL_P(rb_check_to_integer(beg, "to_int")) ||
        !NIL_P(rb_check_to_integer(end, "to_int"))) {
        if (r_le(beg, val)) {
            if (EXCL(range)) {
                if (r_lt(val, end))
                    return Qtrue;
            }
            else {
                if (r_le(val, end))
                    return Qtrue;
            }
        }
        return Qfalse;
    }

r_leとかr_ltとかあるのは、rational numberのlarger equal(than)のことだろうか。EXCLは、...で終点の値をexcludeしているかどうかのフラグかな。FIXNUM_PとかNIL_Pとかって命名規則Lispっぽいような。

肝心のVALUEってのが何なのかがイマイチ分からない。Ruby Hacking Guide(Rubyソースコード完全解説)という優れたドキュメントがある(http://i.loveruby.net/ja/rhg/book/)ので、これを少しずつ眺めていこうかと思う。