8

I could not understand why, in Ruby, Array#slice and Array#slice! behave differently than Array#sort and Array#sort! (in the way that one returns the results on a new Array and the other works on the current object).

With sort the first one (without the bang), returns a sorted copy of the current Array, and sort! sorts the current Array.

slice, returns an Array with the specified range, and slice! deletes the specified range from the current object.

What's the reason the Array#slice! behaves like this instead of making the current object an Array with the specified range?

Example:

a = [0,1,2,3,4,5,6,7,8,9]

b = a.slice( 2,2 )

puts "slice:"
puts "  a = " + a.inspect
puts "  b = " + b.inspect

b = a.slice!(2,2)
puts "slice!:"
puts "  a = " + a.inspect
puts "  b = " + b.inspect

Output:

slice:
  a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  b = [2, 3]
slice!:
  a = [0, 1, 4, 5, 6, 7, 8, 9]
  b = [2, 3]

http://ideone.com/77xFva

Vargas
  • 2,125
  • 2
  • 33
  • 53
  • 2
    Using `!` on a method does not have a set behaviour, it is just there to indicate that the method in some way mutates the input whilst also having the exact same output. You can add it to the end of any method you write. – tpbowden Jan 07 '14 at 17:05

3 Answers3

5

#slice and #slice! behaviors are equivalent: both "return a subarray starting at the start index and continuing for length elements", the same way as #sort and #sort! return a sorted array or #reverse and #reverse! return a reversed array.

The difference is that the bang methods also modify the object itself.

a = [4,2,6,9,1,5,8]
b = a.dup
a.sort == b.sort!             # => true
a == b                        # => false

b = a.dup
a.reverse == b.reverse!       # => true
a == b                        # => false

b = a.dup
a.slice(2,2) == b.slice!(2,2) # => true
a == b                        # => false
wacko
  • 3,384
  • 1
  • 15
  • 22
  • This is not what the user asked. He knows about that, he's asking about a design decision. Specifically, why slice! does not replace the current object with the result. – Simone Carletti Jan 07 '14 at 18:22
  • 3
    `#slice` (and `#slice!`) _"returns nil if the index is out of range"_. That situation is incompatible with the idea of replacing the object with the returned value – wacko Jan 07 '14 at 20:14
  • that's probably one of the best explanation I've read so far. +1 – Simone Carletti Jan 07 '14 at 21:15
  • I'll have to go with that =/ I had the wrong idea about bang-methods. – Vargas Jan 08 '14 at 10:53
1

! or bang methods in ruby typically mutate the existing object rather than the non-bang method equivalent that returns a new object. If you want to modify the existing object, use the bang method option.

EDIT: to address the design decision, I'm not Matz, but I am going to guess that because the nature of slice is to return a subset, it returns the subset in each case. For other bang methods like gsub or sort you are modifying (potentially) the entire string/object so it either returns a copy or returns the original with changes.

Jed Schneider
  • 14,085
  • 4
  • 35
  • 46
0

I think the idea behind the design decision is that if you call Array#slice! then you already have a pointer to the slice of the array (assuming you assigned it to a variable). The logical decision is to modify the object so that the slice is no longer in the original array.

You can see how this would be useful in a loop, for instance. If you wanted to keep taking a slice of the first 3 elements of the array, do something with those 3 elements, and stop when there was no more, you could make use of Array#slice! and it would eventually destroy the array.

Now if you imagine that array from the example was a queue or a stack, it would make sense as to why you want to remove portions of the array.

jcomo
  • 183
  • 6