I think the following captures it quite well:
Okay, firstly, the following works
a = [0]
b = view(a,[1,1]) # or Base.dotview(b,[1,1])
b .= b .+ [1,2]
in the sense that we now have a == [3]
. Naturally, just b = ...
would just assign to b
whatever is right of the equal sign. That makes sense.
However, this is where I get seriously confused and I think this is what you pointed out. Looking at the underlying procedure, we have
julia> Meta.@lower b .= b .+ [1,2]
:($(Expr(:thunk, CodeInfo(
@ none within `top-level scope`
1 ─ %1 = Base.vect(1, 2)
│ %2 = Base.broadcasted(+, b, %1)
│ %3 = Base.materialize!(b, %2)
└── return %3
))))
Here %3 = Base.materialize!(b, %2)
does some magic broadcasting the assignment operation together with the addition. I guess because it first adds, then assigns, then adds, then assigns, instead of twice adding, then twice assigning.
Buuut… now take this:
julia> Meta.@lower a[[1,1]] .= b .+ [1,2]
:($(Expr(:thunk, CodeInfo(
@ none within `top-level scope`
1 ─ %1 = Base.vect(1, 1)
│ %2 = Base.dotview(a, %1)
│ %3 = Base.vect(1, 2)
│ %4 = Base.broadcasted(+, b, %3)
│ %5 = Base.materialize!(%2, %4)
└── return %5
))))
The first two steps are just what we did earlier for b
. The last three steps are literally the three steps above, however, we then end up with a == [2]
? This is seriously weird, if I did not just miss something.
I get that we now have two different views a[[1,1]]
(as appearing as L-value) and b
, so a!===b
, but is this how things should work out?
What surprisingly works as well is view(a,[1,1]) .+= [1,1]
(I made an error here ealier). We get a==[3]
.
But what does not work is view(a,[1,1]) .= view(a,[1,1]) .+ [1,1]
. Here we get a==[2]
, because the first and the second [1,1]
are not ===
equal. Yeah, it can not be right . As it seems, for the first call, [1,1]
is actually evaluated first, and then +=
is interpreted and expanded .
The rest of this post is drifting away from the initial discourse a bit, but maybe I can explain my whole confusion with +=
and .+=
While I have no idea how to deal with the above, the literal expansion of x+=y
really makes my head hurt even though technically it is simple. Take this:
a = [0]
b = view(a,[1,1])
b += [1,2]
Here, a==[0]
, as after the expansion to b = b + 1
, the left b
is now just assigned as the new vector [1,2]
because it is no longer acting as view. To do so, one has to use b .+= [1,2]
, which however at the same times changes the order of assigning and adding, and we get a = [3]
.
On the other hand, with
a = [0]
a[[1,1]] .+= [1,2]
we now have (really just the other way around) a view on the left, but when expanded to the right, now a[[1,1]]
is no longer a view, and as carried out above, we get a==[2]
. This kind of makes me want to avoid +=
altogether.
Is there even a particular reason why a[[1,1]]
as well as a[:]
is not just always a view? I mean, the adjoint a'
is a view, reshapings are views, but vectorization a[:]
and subarrays are not…? If so, then a[[1,1]] .+= [1,2]
(modulo aboves weird behavior) would indeed yield [3]
even without further change of +=
.