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
Slicing and indexing are two different operations, and inferring the behaviour of one from the other is where your problem lies.
The first argument in slice identifies not the element but the places between elements, defining spans (and not elements themselves):
:peanut :butter :and :jelly
0 1 2 3 4
4 is still within the array, just barely; if you request 0 elements, you get the empty end of the array. But there is no index 5, so you can't slice from there.
When you do index (like array[4]
), you are pointing at elements themselves, so the indices only go from 0 to 3.
You need to be able to assign to those slices, so they are defined in such a way that the beginning and the end of the string have working zero-length expressions.
array[4, 0] = :sandwich
array[0, 0] = :crunchy
=> [:crunchy, :peanut, :butter, :and, :jelly, :sandwich]
Consider the following array:
>> array=["a","b","c"]
=> ["a", "b", "c"]
You can insert an item to the begining (head) of the array by assigning it to a[0,0]
. To put the element between "a"
and "b"
, use a[1,0]
. Basically, in the notation a[i,n]
, i
represents an index and n
a number of elements. When n=0
, it defines a position between the elements of the array.
Now if you think about the end of the array, how can you append an item to its end using the notation described above? Simple, assign the value to a[3,0]
. This is the tail of the array.
So, if you try to access the element at a[3,0]
, you will get []
. In this case you are still in the range of the array. But if you try to access a[4,0]
, you'll get nil
as return value, since you're not within the range of the array anymore.
Read more about it at http://mybrainstormings.wordpress.com/2012/09/10/arrays-in-ruby/ .
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.)
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: }
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.
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.