Skip to content

What to do about multivalue setindex!? #24086

@mbauman

Description

@mbauman

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

  1. Deprecate multivalue scalar broadcasting within setindex! 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 that setindex! supports; I believe the deprecation would be:

    `A[I...] = X` is deprecated. Use `A[I...] .= reshape(X, indices(view(A, I...)))` instead

    This is most painful in the common case where X is 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.

  2. Remove the eltype special-casing in dotview. If the indices are scalar, then we should always attempt to modify the element. This is breaking deprecatable. Deprecate array of arrays special casing in .= #24095

  3. In 1.x, we could potentially introduce the syntaxes A.[I] and A.[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 sets x at the scalar index i.
    • A[i] .= X: Always broadcasts X across the element at A[i] (always mutating A[i]). Note that this doesn't make much sense for nonscalar I as mutating A[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: Assigns x to every index in I.
    • A.[I] .= X: Always broadcasts X across the indices selected by I (always mutating A).

Metadata

Metadata

Assignees

No one assigned

    Labels

    arrays[a, r, r, a, y, s]breakingThis change will break codebroadcastApplying a function over a collection

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions