Crystal: Array: Let #[]= append an element to the end of the array

Created on 23 Oct 2019  路  6Comments  路  Source: crystal-lang/crystal

I'm proposing to let array[array.size] = x behave exactly the same as array << x instead of raising IndexError.

Currently, when calling Array#[]=(index : Int, value : T) valid values for index are 0...size (exclusive range). When called with index >= size it raises IndexError:

ary = [1, 2, 3]
ary[2] = 1 # OK
ary[3] = 1 # IndexError
ary[4] = 1 # IndexError

This behaviour is generally correct. The array can't be dynamically resized to index because array[size...index] would be void. There is no default value for array elements.

For the edge case index == size, however this is not a problem: It would not create a gap, simply append an element to the end.

ary = [1, 2, 3]
ary[2] = 1 # OK
ary[3] = 1 # OK
ary[4] = 1 # IndexError

I encountered a use case for this when using an array as a stack with a stack_pointer referencing the array index. When the pointer moves to the right, it needs to make sure the array grows.

# this is how I tried to do it:
stack_pointer += 1
stack[stack_pointer] = nil

# this is how it ended up:
stack_pointer += 1
if stack.size <= stack_pointer
  stack.push nil
 end

With this change, the first alternative would work as expected.

Most helpful comment

I think there can be many "off by one" errors that with this change will start to work, incorrectly.

Also, for stack usage you can use push and pop.

All 6 comments

I'm not sure.

[]= mutates an array in place with no side effect: the array won't be resized or reallocated, unlike growing (or possibly shrinking) mutators like push where the resize is expected. With that change, there would be one special case where []= would suddenly grow the array. This is IMO inconsistent and unexpected, and could silence an actual error.

BTW: your example would never raise an IndexError but grow the array, unless you create a gap in indexes, for example jumping from index 3 to 5.

I think there can be many "off by one" errors that with this change will start to work, incorrectly.

Also, for stack usage you can use push and pop.

What about making #insert have this behavior?

Wouldn't #[]= working in some cases but not others be more confusing?

Explicitly named method #insert mentioned by @Blacksmoke16 can be less confusing for this case, but I'm not sure.

insert already changes the length of the array, and appending elements would be consistent with the existing behavior.

Closing. We won't implement this.

Inserting at the end of an array could be semantically correct. It's basically #push but with the ability to insert at any place within 0..size (inclusive) instead of 0...size (exclusive) when we can already insert at zero (basically #unshift), but it wouldn't solve the initial issue, which isn't about inserting, but replacing and sometimes have an insertion that can already be achieved with #push.

Was this page helpful?
0 / 5 - 0 ratings