Feature #4541

Inconsistent Array.slice()

Added by kbl (Marcin Pietraszek) about 1 year ago. Updated 7 months ago.

[ruby-core:<unknown>]
Status:Assigned Start date:03/31/2011
Priority:Normal Due date:
Assignee:matz (Yukihiro Matsumoto) % Done:

0%

Category:core
Target version:2.0.0

Description

Array slice/[] method is a bit inconsistent. Is it just poorly documented "feature" or a bug? In API doc I can't find this behaviour mentioned as a "special case".

def test_array_slice
   array = ['a', 'b', 'c']
   assert_equal nil, array[3]
   assert_eaual nil, array[4]

   assert_eaual [], array[3, 0] #
   assert_equal nil, array[4, 0] # [] expected (or both nils in array[3, 0] and array[4, 0])

   assert_equal ['c'], array[2..2]
   assert_equal [], array[3..3] #
   assert_equal nil, array[4..4] # [] expected (or both nils in array[3..3] and array[4..4])
end

Same behaviour can be reproduced on ruby 1.8.7 (2010-12-23 patchlevel 330) [x86_64-linux].


Related issues

duplicates ruby-trunk - Bug #4245: Array index with Range inconsistency Rejected 01/07/2011

History

Updated by naruse (Yui NARUSE) about 1 year ago

  • Status changed from Open to Assigned
  • Assignee set to matz (Yukihiro Matsumoto)

The fix may be following but it can be a spec...

diff --git a/array.c b/array.c index bdeb768..4721387 100644

a/array.c

+++ b/array.c @@ -948,7 +948,7 @@ rb_ary_subseq(VALUE ary, long beg, long len)

{
    VALUE klass;

- if (beg > RARRAY_LEN(ary)) return Qnil;

if (beg >= RARRAY_LEN(ary)) return Qnil;
if (beg < 0 || len < 0) return Qnil;

if (RARRAY_LEN(ary) < len || RARRAY_LEN(ary) < beg + len) {

Updated by marcandre (Marc-Andre Lafortune) about 1 year ago

Hi,

On Tue, Apr 5, 2011 at 3:04 AM, Yui NARUSE <redmine@ruby-lang.org> wrote: > > Issue #4541 has been updated by Yui NARUSE. > > Status changed from Open to Assigned > Assignee set to Yukihiro Matsumoto > > The fix may be following but it can be a spec... > > diff --git a/array.c b/array.c > index bdeb768..4721387 100644 > --- a/array.c > +++ b/array.c > @@ -948,7 +948,7 @@ rb_ary_subseq(VALUE ary, long beg, long len) >  { >     VALUE klass; > > -    if (beg > RARRAY_LEN(ary)) return Qnil; > +    if (beg >= RARRAY_LEN(ary)) return Qnil; >     if (beg < 0 || len < 0) return Qnil; > >     if (RARRAY_LEN(ary) < len || RARRAY_LEN(ary) < beg + len) {

With all due respect, I am strongly opposed to that change. There is no way that the following results should change:

array[0...3] # => ["a", "b", "c"]
array[1...3] # => ["b", "c"]
array[2...3] # => ["c"]
array[3...3] # => [], as it *must* be.

The only possible change would be that array[42..42] returns [] instead of nil. For consistency's sake, `String#slice` would also have to be changed. I am not in favor of that change either, for compatibility reason and because I feel the current API makes sense. See my comment on issue #4245 [ruby-core:34197]. This issue is a duplicate of #4245.

Updated by zimbatm (Jonas Pfenniger) about 1 year ago

I don't see the advantage of having nil returned in any case since the empty array already expresses the "there is no object in that range".

Out of bound can be tested separately if necessary, but most of the cases you just want to get a range and a resulting array. Having also nil being returned means that you need some more code to test return.nil? && return.empty?

Updated by nahi (Hiroshi Nakamura) 11 months ago

  • Target version changed from 1.9.2 to 1.9.3

Updated by nobu (Nobuyoshi Nakada) 11 months ago

This is not a bug. I'm against the spec change of this behavior.

Updated by rosenfeld (Rodrigo Rosenfeld Rosas) 11 months ago

Nobuyoshi, could you explain why you don't find the current behavior confusing and would like to keep it as it is? What would be a useful use case for the current implementation?

Updated by gwright (Gary Wright) 11 months ago

This discussion might help explain the rational for the current behavior: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/380636 There is also a related rationale in this posting: http://www.phwinfo.com/forum/comp-lang-ruby/462990-array-index-length-strange-behaviour.html#post2148794

Updated by marcandre (Marc-Andre Lafortune) 11 months ago

  • Tracker changed from Bug to Feature

Updated by marcandre (Marc-Andre Lafortune) 11 months ago

  • Category set to core
  • Target version changed from 1.9.3 to 2.0.0
Changing to "feature" as this is a spec change. As I stated earlier, I'm against either propositions.

Updated by rosenfeld (Rodrigo Rosenfeld Rosas) 11 months ago

What I think is less confusing is to always return an array when passing ranges, like the following example: <pre> [][1..2] #=> [] instead of nil </pre> Think in some practical method like this one: <pre> def some_method(*elements) # suppose elements can be [], maybe elements are read from some file... special_element = args[0] # suppose we don't want elements.shift because we will send elements to another method... do_something_special if special_element elements[1..-1].each{|el| ...} # this will raise an exception if elements == [] call_another_method(elements) end </pre> Yes, I know this is not a great example, but I'm too tired right now to think in a better one...

Updated by alexeymuranov (Alexey Muranov) 7 months ago

I better remove my previous comment, it was not a good idea. I agree that there seem to be an inconsistency between `[1,2,3][3..1] # => []` and `[1,2,3][4..1] # => nil`. In my opinion, the confusion about the behavior of `Array#slice` could be coming from the difficulties in choosing good definitions for `Range` and `Array`. For example, the ranges `(3..1)`, `(4..1)`, `(1..-1)` all have no elements, but can be used to slice an array, all in different ways. It could be easier if there existed only one empty range: `(2..1) == (1..-1) and (2..1) == (1...1) and (2..1) == ('z'..'a') # => true`. But then there would come the problem of not being able to slice an array from the beginning and the end simultaneously with `a[1..-2]`. This can be resolved by introducing `RelativeRange` and `RelativeNumber` and do like this: `a[1.from_bottom..(-1).from_top]`. (The method `#include?` would not be defined for `RelativeRange` and relative numbers with different "anchors" would not be comparable; "anchor" would be an arbitrary symbol, like `:top` or `:bottom`.) Just an idea. To me, array is an object between string and hash, so how about viewing array as a special kind of hash, where only integer keys are allowed, and the keys come in pairs (`[1,2,3][1] == [1,2,3][-2]`), and think from there? (Strings should not probably be viewed as hash because splitting into characters is not very well defined for accented or otherwise decorated strings.) These are just my ideas. -Alexey. Update: i made a feature request #5534 for RelativeNumeric and RelativeRange. Last edited 2011-11-01

Also available in: Atom PDF