Jonas Schäfer

What the Heck: The Ruby Array.slice method

This post uses ruby 2.5.3p105 (2018-10-08 revision 65156) from Debian testing.

Let me start this off with a snippet from the irb ruby interactive shell:

irb(main):001:0> [1, 2, 3].slice(1, 10)
=> [2, 3]
irb(main):002:0> [1, 2].slice(1, 10)
=> [2]
irb(main):003:0> [1].slice(1, 10)
=> []

This makes sense so far. The Array.slice method allows you to get a range of elements from an array. It takes the first index and the number of items you want to get. It is also gracious enough to allow you to specify a larger amount of items than available in that range and it will silently truncate the result.

I consider this fairly sensible behaviour; Python slices behave the same, and they have been very useful. In contrast to direct access to an array element, it does not make sense to require the user to specify the exact range here.

However, this is where it becomes weird.

Despite being gracious about the number of items, there is simply no non-ugly way to say "give me all items starting at index i" (passing nil will give a TypeError, passing nothing at all is equivalent to passing 1). Sure, you can do:

irb(main):004:0> a = (1..10).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
irb(main):005:0> a.slice(5, a.length)
=> [6, 7, 8, 9, 10]

And it does the right thing. However (and this might very well be my twisted matter of taste), I find it weird to pass an argument which is not exactly the right amount of items in all cases and also not a value which represents "all items" in all cases for all arrays.

Alas, this isn’t why I made this post. The following is:

irb(main):006:0> [].slice(1, 10)
=> nil
irb(main):007:0> [].slice(1, 0)
=> nil

What the Heck, Ruby?! Who thought this might be a good idea?

What makes [].slice(1, 10) so different from [1].slice(1, 10)? Both refer to a range of items not actually in the array on which the method was called. So why do they return different values, yes, even different types?

But it gets more fun:

irb(main):008:0> [].slice(0, 0)
=> []
irb(main):009:0> [].slice(0, 1)
=> []

Like... What?! I don’t even...

At this point, I’m not sure if I’m looking at a slice function or at the exponentiation operator (which has similarly definition-dependent and rather odd behaviour when zeroes are involved).