負のインデックス

array.cを少し。pop、shift、[]=あたりを見ていると、何だかRubyがCに見えてくる。Arrayオブジェクトって、結局はVALUE型、つまりunsigned longの配列でしかない。ちょっと今さら驚いたのは、memcpy、memmoveがコピー先、コピー元で重複があってもいいというところとか。そうだったのか。

 822 /*
 823  *  call-seq:
 824  *     array.unshift(obj, ...)  -> array
 825  *  
 826  *  Prepends objects to the front of <i>array</i>.
 827  *  other elements up one.
 828  *     
 829  *     a = [ "b", "c", "d" ]
 830  *     a.unshift("a")   #=> ["a", "b", "c", "d"]
 831  *     a.unshift(1, 2)  #=> [ 1, 2, "a", "b", "c", "d"]
 832  */
 833 
 834 static VALUE
 835 rb_ary_unshift_m(int argc, VALUE *argv, VALUE ary)
 836 {
 837     long len;
 838 
 839     if (argc == 0) return ary;
 840     rb_ary_modify(ary);
 841     if (ARY_CAPA(ary) <= (len = RARRAY_LEN(ary)) + argc) {
 842         ary_resize_capa(ary, len + argc + ARY_DEFAULT_SIZE);
 843     }
 844 
 845     /* sliding items */
 846     MEMMOVE(RARRAY_PTR(ary) + argc, RARRAY_PTR(ary), VALUE, len);
 847     MEMCPY(RARRAY_PTR(ary), argv, VALUE, argc);
 848     ARY_INCREASE_LEN(ary, argc);
 849     
 850     return ary;
 851 }

unshiftだとニュルンとarrayを引き伸ばしてるけど、逆にpopだとarrayのメモリ上のサイズを縮めたりしている。

 703 VALUE
 704 rb_ary_pop(VALUE ary)
 705 {
 706     long n;
 707     rb_ary_modify_check(ary);
 708     if (RARRAY_LEN(ary) == 0) return Qnil;
 709     if (ARY_OWNS_HEAP_P(ary) &&
 710         RARRAY_LEN(ary) * 3 < ARY_CAPA(ary) &&
 711         ARY_CAPA(ary) > ARY_DEFAULT_SIZE)
 712     {
 713         ary_resize_capa(ary, RARRAY_LEN(ary) * 2);
 714     }
 715     n = RARRAY_LEN(ary)-1;
 716     ARY_SET_LEN(ary, n);
 717     return RARRAY_PTR(ary)[n];
 718 }
 719 

ちょっとおもしろいなと思ったのは、709〜711行目。ARY_CAPAは確保済みのメモリ容量から来る利用可能サイズ。即値埋め込みの場合もあるのでマクロになっているっぽい。で、arrayの長さが確保済みの長さの3分の1以下で、かつARY_DEFAULT_SIZE(16)よりも大きい場合には、713行目で縮めている。この辺の、いつ、どの程度伸び縮みさせるかって経験則で決めてるのかな。

Array#[]=を見て、ちょっとあれっと思った。idxが負のとき、お尻から数えるのはいいとして、配列サイズの絶対値より大きい場合には、例外を上げている。

 567 void
 568 rb_ary_store(VALUE ary, long idx, VALUE val)
 569 {
 570     if (idx < 0) {
 571         idx += RARRAY_LEN(ary);
 572         if (idx < 0) {
 573             rb_raise(rb_eIndexError, "index %ld out of array",
 574                      idx - RARRAY_LEN(ary));
 575         }
>> a = [1,2,3]
=> [1, 2, 3]
>> a[-1] = 9
=> 9
>> a
=> [1, 2, 9]
>> a[-3] = 10
=> 10
>> a
=> [10, 2, 9]
>> a[-5] = 10
IndexError: index -5 out of array
        from (irb):6:in `[]='
        from (irb):6
        from /usr/local/bin/irb:12:in `<main>'

正方向に範囲外を指定したときには配列を自動的に伸ばすのに負方向のときは例外というのは一貫性がないようにも感じる。

>> a = [1,2,3]
=> [1, 2, 3]
>> a[5] = 10
=> 10
>> a
=> [1, 2, 3, nil, nil, 10]

なのだから、

>> a = [1,2,3]
=> [1, 2, 3]
>> a[-5] = 10
=> 10
>> a
=> [10,nil,1, 2, 3]

でもいいような気がするんだけど。