Julia language problem with assigning to an array using both subscripts and linear indexing

Viewed 98

Coming from R, I'm relatively new to the Julia language.

Sometimes I come across a situation where I'd like to use two passes of indexing within the same command: first with a set of subscripts, and then with a linear index. Such as:

array = zeros(3, 3, 3)
array[:, :, 2][9] = 999

Julia responds by returning the answer, as expected:

julia> array[:, :, 2][9] = 999
999

However, the original array is unchanged:

julia> array[:, :, 2]
3×3 Array{Float64,2}:
 0.0  0.0  0.0
 0.0  0.0  0.0
 0.0  0.0  0.0

Is this a bug, or is there some fact about the language that I'm missing, to explain why it behaves this way?

1 Answers

Short answer

Use

@views array[:, :, 2][9] = 999

More details

The thing to know here is that x[1] = y is lowered to a single function call to setindex!, no matter what x is. This is a normal function, and so all of its arguments are evaluated before the function is called. Here is how you can see what is being done:

julia> set1(x) = x[1] = 33
set1 (generic function with 1 method)

julia> @code_lowered set1(zeros(3))
CodeInfo(
1 ─     Base.setindex!(x, 33, 1)
└──     return 33
)

julia> :( z[1,:][2] ) # the brackets show precedence, else try |> dump
:((z[1, :])[2])

julia> set2(z) = z[1,:][2] = 3^4;

julia> @code_lowered set2(zeros(2,3))
CodeInfo(
1 ─ %1 = Core.apply_type(Base.Val, 4)
│   %2 = (%1)()
│   %3 = Base.literal_pow(Main.:^, 3, %2)
│   %4 = Base.getindex(z, 1, Main.:(:))
│        Base.setindex!(%4, %3, 2)
└──      return %3
)

When x is another indexing expression, this is just the function call getindex(z,1,:). That makes a copy, and setindex! writes into the copy, and then returns its right hand side (the temporary variable %3, containing 3^4). The copy never escapes.

The way to get the behaviour you want is to make x a view not a copy, by writing view(z,1,:)[2] = 33 or:

julia> set3(z) = @views z[1,:][2] = 33;

julia> @code_lowered set3(zeros(2,3))
CodeInfo(
1 ─ %1 = (Base.maybeview)(x, 1, Main.:(:))
│        Base.setindex!(%1, 33, 2)
└──      return 33
)

julia> z3 = zeros(2,3);

julia> set3(z3)
33

julia> z3
2×3 Matrix{Float64}:
 0.0  33.0  0.0
 0.0   0.0  0.0

It would certainly have been possible to lower z[1,:][2] = 3 to something else, some function like multi_setindex!(z, 3, (1,:), (2,)) perhaps. I guess that was judged to be too much complication.

I think that R has non-standard evaluation, under which a function can reach into its arguments before they are evaluated, but Julia doesn't allow this. It does have macros (always denoted with @) which get to act on the input syntax before evaluation. This is necessary for @views to work, and of course for @code_lowered to see something other than 33 or 9.

Related