-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
Description
Problem
We have two ways to assign many values to many locations in an array:
A[2:3] = 4:5
A[2:3] .= 4:5
In this simple case, both of these do the same thing for any array: assign A[2] = 4 and A[3] = 5. But in more complicated cases, both syntaxes are quirky in different ways:
setindex!: A[I...] = X
X must have the same number of elements as the indices I select. Shapes are compared, but singleton dimensions are ignored and the final dimension of X is allowed to linearly span multiple indices. If X is not a subtype of AbstractArray, then the same value is simply broadcast to all indices. Examples:
A = rand(4,1,3,2);
A[:,:,:,:] = 1:24 # Linear spanning
A[:,:,:,:] = reshape(1:24, 4,3,2) # Ignoring singleton dimensions in I
A[:,:,:,:] = reshape(1:24, 4,3,1,2) # Ignoring singleton dimensions in both X and I
A[:,:,:,:] = reshape(1:24, 4,6) # Ignoring singleton dimensions in I and linear spanning
A[:,:,:,:] = 1 # fills A with ones
Now, if I only selects exactly one index then this syntax always puts X into that location:
julia> A = Any[1,2,3];
A[1] = 4:5;
A
3-element Array{Any,1}:
4:5
2
3
This is quirky when you actually want to generically assign a single value to multiple locations… and you don't care about its type! Ref. #22747.
broadcast: A[I...] .= X
Broadcasting dictates that the shape of the indexing expression and the shape of X have exactly the same shape if they have the same number of elements. Absolutely no linear spanning is allowed. All of the setindex! examples above are errors except for the scalar 1 case. At the same time, though, .= adds the full set of broadcast features, allowing partial arrays to broadcast across the complete set of indices.
Now, if I only selects exactly one index, then this syntax is quirky:
julia> A = [[1,2,3],4:5,6]
A[1] .= 0
A
3-element Array{Any,1}:
0
4:5
6
julia> A = [[1,2,3],4:5]
A[1] .= 0
A
2-element Array{AbstractArray{Int64,1},1}:
[0, 0, 0]
4:5
This special-casing on the element type is attempting to work around awkwardness due to the scalar/nonscalar distinction in number of indices. That is, A[I] .= 0 modifies A if I is nonscalar, but it should modify the element if I is scalar.
Proposed solution
-
Deprecate
multivaluescalar broadcasting withinsetindex!in favor of the.=broadcasting syntax..=is a very nice, explicit signal that you want to broadcast the values on the RHS.The major issue with this deprecation is all the shape permutations thatsetindex!supports; I believe the deprecation would be:`A[I...] = X` is deprecated. Use `A[I...] .= reshape(X, indices(view(A, I...)))` insteadThis is most painful in the common case whereXis a one-dimensional linear span over a set of indices that is not linear.After attempting to deprecate multivalue scalar indexing in RFC: Deprecate multi-value non-scalar indexed assignment #24368, we realized that it made way more sense to instead deprecate the scalar broadcasting behaviors of setindex. This was done in RFC: Deprecate implicit scalar broadcasting in setindex! #26347.
-
Remove the
eltypespecial-casing indotview. If the indices are scalar, then we should always attempt to modify the element. This isbreakingdeprecatable. Deprecate array of arrays special casing in .= #24095 -
In 1.x, we could potentially introduce the syntaxes
A.[I]andA.[I] .= X. Those are errors at the moment, so we can choose their exact behaviors later. But, they'd allow us to fully detangle the scalar/nonscalar behaviors if we wished:A[i] = x: Always setsxat the scalar indexi.A[i] .= X: Always broadcastsXacross the element atA[i](always mutatingA[i]). Note that this doesn't make much sense for nonscalarIas mutatingA[I]is mutating an unobservable temporary. We would't be able to change this behavior in 1.x, but we could deprecate it in preparation for 2.0.A.[I] = x: Assignsxto every index inI.A.[I] .= X: Always broadcastsXacross the indices selected byI(always mutatingA).