Dot-assignment ... what is going on here?

question
#1

Run the following example:

x = zeros(2)
y = zeros(3)
[x;y] .= [1.2, 4.5, 2.3, 4.5, 5.6]

Surprisingly (for me at least), x,y are still zero after this. Why?

I am using v0.5.0.

1 Like

#2

[x;y] constructs a new array that copies the content of x and y but has no connection to x and y after being constructed. You are assigning to that new array, not the original x and y.

3 Likes

#3

Often the best way to examine these sorts of issues is by asking Julia itself. The expand function removes any “syntactic sugar” and displays the code that Julia ends up running. In this case:

julia> expand(:([x;y] .= [1.2, 4.5, 2.3, 4.5, 5.6]))
:((Base.broadcast!)(Base.identity,vcat(x,y),(Base.vect)(1.2,4.5,2.3,4.5,5.6)))

That is, it’s broadcasting the identity function over your array, and then storing the answer into the result of vcat(x,y). It’s the result of that vcat operation that gets mutated… but you don’t have a reference to it, so you never see it happen.

7 Likes

#4

I feel like we may want to syntactically disallow this, unless anybody can think of a useful purpose for this syntax.

1 Like

#5

It’s hard for the parser to know which functions return a view and which create a new object.

I have recently used view(...) .= ... and the result was fast to execute, while being expressive and easy to read.

0 Likes

#6

@andyferris But view([x;y]) .= [1.2, 4.5, 2.3, 5.6, 2.3] still doesn’t work (it gives an error).

Maybe one could conceive a generalization of view. Currently, view only works to return a subarray, where it transparently maps indexes on the view to indices on the original array. It could be just as useful to have a function to map indexes to a super array, like a block matrix. For example, suppose I want to work with the block matrix:

M = [A11 A12; A21 A22]

without having to instantiate it (maybe the A's are too large). I would like a transparent way of refering to the index M[i,j], where it automatically maps this index to the appropriate index in the appropriate A.

0 Likes

#7

See CatViews.jl.

6 Likes

#8

@ChrisRackauckas Nice!

using CatViews
x = zeros(2)
y = zeros(3)
CatView(x,y) .= [1.2, 4.5, 2.3, 4.5, 5.6]

works:

julia> [x;y]
5-element Array{Float64,1}:
 1.2
 4.5
 2.3
 4.5
 5.6
0 Likes

#9

No, I meant use view instead of getindex.

E.g. using the @view macro,

@view(a[:, :, 3]) .+= myvector
0 Likes

#10

Nice, @ChrisRackauckas

0 Likes

#11

The only useful meaning I could see this having writing data in place to the two arrays, which would actually be pretty cool, but I’m not sure how reasonably we can add that functionality to Base Julia (it can be done via CatViews as demonstrated). It might make sense to disallow the syntax until it can be supported and then re-allow it.

0 Likes

#12

Note that this is a bug; see e.g. https://github.com/JuliaLang/julia/issues/20623.

0 Likes

#13

@mbauman: is there an equivalent to expand in Julia v1?

0 Likes

#14

Yes, it’s now called Meta.@lower and you no longer need to worry about quoting. The internal machinations of broadcast have changed, too, but the end result in this context is the same.

julia> Meta.@lower [x;y] .= [1.2, 4.5, 2.3, 4.5, 5.6]
:($(Expr(:thunk, CodeInfo(
1 ─ %1 = (Base.vcat)(x, y)
│   %2 = (Base.vect)(1.2, 4.5, 2.3, 4.5, 5.6)
│   %3 = (Base.broadcasted)(Base.identity, %2)
│   %4 = (Base.materialize!)(%1, %3)
└──      return %4
))))
3 Likes

#15

I am still a bit confused by how dot assignment is lowered:

a = randn(3)
b = randn(3)
view(a,:) .= view(b,:) # this works
a .+= b # this works
view(a,:) .+= view(b,:) # this does not work

Unfortunately, Meta.@lower doesn’t make me any wiser:

julia> Meta.@lower a .+= b
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope'
1 ─ %1 = Base.broadcasted(+, a, b)
│   %2 = Base.materialize!(a, %1)
└──      return %2
))))

julia> Meta.@lower view(a,:) .= view(b,:)
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope'
1 ─ %1 = view(a, :)
│   %2 = view(b, :)
│   %3 = Base.broadcasted(Base.identity, %2)
│   %4 = Base.materialize!(%1, %3)
└──      return %4
))))

julia> Meta.@lower view(a,:) .+= view(b,:)
:($(Expr(:error, "invalid assignment location \"view(a, :)\"")))
0 Likes

#16

That’s a false-positive from the parser — I’d say that’s a bug. Mind filing an issue?

In this particular case, you can use @views a[:] .+= b[:] to work around it (if this is indeed the form of your original problem that led you here).

1 Like

#17

Thanks for the response; issue filed here: https://github.com/JuliaLang/julia/issues/31295

1 Like