Setting array values through sequential application of two sets of indices

I recently ran into a bug in my code related to some unintuitive (to me) behavior of array indexing.

Given an array, say

A = reshape(collect(1:16),(4,4))

we can access elements of the array through multiple indexing calls

A[1:2,3:4][1:2]
# [9, 10]

However, what I was unaware of was that it is not possible to set values through sequential applications of array indexing, i.e.

A[1:2,3:4][1:2] = [1;1];
A[1:2,3:4][1:2]
# [9,10]

However, I figured out that I can get this to work through the use of views

B = view(A,1:2,3:4);
B[1:2] = [1;1];
A[1:2,3:4][1:2]
# [1,1]

Could someone please explain why I shouldn’t expect the first way to work, i.e. why sequential applications of indexing does not allow for setting array value?

The key reason is because Julia’s indexing returns copies that are independent of the original array, and the syntax B[J1][J2] = X is actually doing (B[J1])[J2] = X. It’s evaluating B[J1] first, and then performing the indexed assignment on that result. Unfortunately, you’ll never be able to see that mutation because B[J1] has copied the values from B into a new array — an array that you’re not holding onto — and then that array is mutated. But you’ll never see that mutation since you didn’t hold onto the copy that resulted from indexing!

Views work because they return, well, a view into the original array. Mutations to the view get reflected in the original array. So, in fact, you can use the @views macro and then this syntax will work as you expected:

julia> @views A[1:2,3:4][1:2] = [1;1];

julia> A
4Ă—4 Array{Int64,2}:
 1  5   1  13
 2  6   1  14
 3  7  11  15
 4  8  12  16

This all stems from very simple syntax rules that transform indexed assignment into setindex! calls and indexing in general into getindex calls. If you’re interested in digging in more here, you can peel back the curtain and look under the hood with Meta.@lower A[I] = X (on 0.7) or expand(:(A[I] = X)) (on 0.6). Check out what happens when you add a second set of indices or the @views macro.

3 Likes

Makes sense. Thanks!