26

If I have an array a:

  1. a[a.length] returns nil. Good.
  2. a[a.length, x] returns []. Good.
  3. a[a.length+x, y] returns nil. Inconsistent with 2.

While this behavior is documented, it seems odd.

Can anybody explain the reasons behind this design?

Diego Mijelshon
  • 52,548
  • 16
  • 116
  • 154
  • 3
    See also [Array slicing in Ruby: looking for explanation for illogical behaviour (taken from Rubykoans.com)](http://stackoverflow.com/questions/3568222/array-slicing-in-ruby-looking-for-explanation-for-illogical-behaviour-taken-fro) – Phrogz Sep 08 '11 at 12:39

3 Answers3

29

Consider this

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

So a[4, 10] is the slice between the 3 and the end of the array which is []

Where as a[4] and a[5, 10] are accessing elements that aren't in the array

It may help to think of the slice points as being between the elements, rather than the elements themselves.

[ <0> 0 <1> 1 <2> 2 <3> 3 <4> ]

Where <n> are the points between elements and the start/end of the array. a[4, 10] then becomes a selection of 10 elements, starting from point 4. Whereas a[5, 10] starts from point 5, which is not part of the list.

3limin4t0r
  • 19,353
  • 2
  • 31
  • 52
John La Rooy
  • 295,403
  • 53
  • 369
  • 502
  • 1
    The first parameter is declared as the "start" position, which is already past the end of the array (and is why `a[a.length]` is `nil`). The example makes a lot less sense if you use anything other than the indexes as the elements. – Diego Mijelshon Jul 10 '10 at 13:33
  • @Diego Mijelshon, Changing the elements to `a=['a','b','c','d']` works exactly the same, the length of the result goes from 4 to 3 to 2 to 1 to 0 and finally `nil` is returned – John La Rooy Jul 10 '10 at 13:47
  • So, it's just an implementation detail. No philosophy behind it? In Python, anything outside the range returns `[]`. In .NET, `ArraySegment` would throw, and Linq returns an empty sequence. (I'll still upvoting your answer, but I'm really disappointed at the language) – Diego Mijelshon Jul 10 '10 at 15:32
  • 1
    This looks like a bug to me, or a documentation error. The docs state that nil is returned if the index or starting index is outside of the range so a[4] and a[4,10] should both return nil – Steve Weet Jul 10 '10 at 19:21
  • I'm with Diego here - although I understand that the behaviour is as document, it feels inconsistent. To me (with a C/C++/C#) background, I'd expect the example to give either: [0,1,2,3], [1,2,3], [2,3], [3], [], [], ... Or [0,1,2,3], [1,2,3], [2,3], [3], nil, nil, ... – Steve Strong Jul 10 '10 at 19:41
  • 1
    It's a Lispy thing. The list (1 2) = (cons 1 (cons 2 nil)). The nil marks the end of a properly formed list. There is a chapter in SICP that explains it. Look at this question http://stackoverflow.com/questions/2921912/in-sicp-exercise-2-26-using-drscheme-why-does-cons-return-a-list-instead-of-a-p that explains a bit. There may be better ones out there somewhere. It is **not** inconsistent. – Allen Jul 10 '10 at 21:10
  • 1
    @Diego, It may help to think of the slice points as being _between_ the elements, rather than the elements themselves. – John La Rooy Jul 11 '10 at 01:29
  • @gnibbler believe me, it's not that I don't get it. It just "feels" wrong (by wrong I mean error-prone). And if the semantics of a[x,y] are so different from a[x], a different operator should be used. – Diego Mijelshon Jul 11 '10 at 04:06
  • 1
    @Steve Weet, look at the special cases in the linked docs. It behaves as documented. – Diego Mijelshon Jul 11 '10 at 04:07
  • @Allen that feeds my impression that an implementation detail or a "legacy" artifact is playing an unnecessary role in the model. – Diego Mijelshon Jul 11 '10 at 04:13
  • @Diego Mijelshon No. It is part of the definition of the data structure you are all discussing. Without it, once the last element is removed, *you would not be left with the empty list*. Are you one of those people who believe that nil does not exist? – Allen Jul 11 '10 at 14:35
  • @Allen I do "believe" in nil (I call it null, though ;-)). But Ruby is supposed to be a high level language. I'm fine with having a \0 or \0\0 in C code, but I don't need terminators in C#, Java or Python because the supporting data structures abstract the implementation details. – Diego Mijelshon Jul 11 '10 at 15:51
  • @Diego M. Oops. I was talking about lists but everyone else is talking about **Array**! I have to agree now that this is curious. Sorry! – Allen Jul 11 '10 at 17:43
  • 4
    I was beating myself over this the other day while working with Ruby Koans. I found it really inconsistent and odd, but gnibbler's suggestion to "think of the slice points as being between the elements, rather than the elements themselves" really clears this all up for me. Thanks! – enriquein Aug 17 '10 at 01:47
  • @enriquein, I've added that to my answer now so people don't have to dig through the comments – John La Rooy Aug 17 '10 at 04:36
6

Look to your friendly Lispy languages for the answer. The philosophy you're looking for began with languages whose specialty was LISt Processing (thus, LISP). For instance, here's one way of creating lists in Haskell:

1:[] => [1] 
1:2:3:[] => [1,2,3] 

This is called cons-ing, for 'constructing' a list. If the idea hasn't clicked yet, consider this: an array is created by adding elements to an empty list, not to 'nil'.

Mugabo
  • 753
  • 7
  • 13
3

To begin with, this case is a special case in Ruby.

This special case also has an explanation to it:

There is a point of difference when you speak about indexing an array, and slicing it.

Indexing an array means to have an unique Position which helps you access the value at a given Index.

Slicing on the other hand means to "cut" between two points (P.S points here are index)

Consider this:

array = [Ruby, PHP, JS, HTML, CSS]

Indexing in this case will be:

array = [Ruby, PHP, JS, HTML, CSS]
Index =   0.    1.  2.  3.    4. 

Slicing in the same case will be:

array = [Ruby, PHP, JS, HTML, CSS]
Slice = 0.   1.   2.  3.    4.   5.

Hence:

array[5,n] #[] i.e. you get an empty array.
array[6,n] #nil i.e. NIL

Reference

Abhishek Jain
  • 630
  • 6
  • 26