Array slicing in Ruby: explanation for illogical behaviour (taken from Rubykoans.com)

后端 未结 10 2463
没有蜡笔的小新
没有蜡笔的小新 2020-11-22 10:39

I was going through the exercises in Ruby Koans and I was struck by the following Ruby quirk that I found really unexplainable:

array = [:peanut, :butter, :a         


        
10条回答
  •  北荒
    北荒 (楼主)
    2020-11-22 11:13

    tl;dr: in the source code in array.c, different functions are called depending on whether you pass 1 or 2 arguments in to Array#slice resulting in the unexpected return values.

    (First off, I'd like to point out that I don't code in C, but have been using Ruby for years. So if you're not familiar with C, but you take a few minutes to familiarize yourself with the basics of functions and variables it's really not that hard to follow the Ruby source code, as demonstrated below. This answer is based on Ruby v2.3, but is more or less the same back to v1.9.)

    Scenario #1

    array.length == 4; array.slice(4) #=> nil

    If you look at the source code for Array#slice (rb_ary_aref), you see that when only one argument is passed in (lines 1277-1289), rb_ary_entry is called, passing in the index value (which can be positive or negative).

    rb_ary_entry then calculates the position of the requested element from the beginning of the array (in other words, if a negative index is passed in, it computes the positive equivalent) and then calls rb_ary_elt to get the requested element.

    As expected, rb_ary_elt returns nil when the length of the array len is less than or equal to the index (here called offset).

    1189:  if (offset < 0 || len <= offset) {
    1190:    return Qnil;
    1191:  } 
    

    Scenario #2

    array.length == 4; array.slice(4, 0) #=> []

    However when 2 arguments are passed in (i.e. the starting index beg, and length of the slice len), rb_ary_subseq is called.

    In rb_ary_subseq, if the starting index beg is greater than the array length alen, nil is returned:

    1208:  long alen = RARRAY_LEN(ary);
    1209:
    1210:  if (beg > alen) return Qnil;
    

    Otherwise the length of the resulting slice len is calculated, and if it's determined to be zero, an empty array is returned:

    1213:  if (alen < len || alen < beg + len) {
    1214:  len = alen - beg;
    1215:  }
    1216:  klass = rb_obj_class(ary);
    1217:  if (len == 0) return ary_new(klass, 0);
    

    So since the starting index of 4 is not greater than array.length, an empty array is returned instead of the nil value that one might expect.

    Question answered?

    If the actual question here isn't "What code causes this to happen?", but rather, "Why did Matz do it this way?", well you'll just have to buy him a cup of coffee at the next RubyConf and ask him.

提交回复
热议问题