The motivation for y = x
evaluating to x
is this:
- The value that
y
actually ends up with can be arbitrarily different from x
since assignment to y
can implicitly call convert
, which can do anything.
- Since Julia is expression-oriented, we want
y = x
to be an expression and evaluate to some value.
- The classic way to make chained assignment work is to parse
z = y = z
as z = (y = x)
and have y = x
evaluate to the value you want to be assigned to z
.
If the value of x
and the value that y
gets are the same, then it doesn’t matter whether y = x
evaluates to x
or y
because they’re the same. But due to point 1, they’re not the same in Julia—this is true in many languages, but it’s worse for us because convert
is user-extensible so someone can do something arbitrarily whacky. But even a relatively innocuous conversion like Int
to Float64
could be problematic. I’m going to simulate z = y = x
where y = x
evaluates to y
instead of x
by writing out y = x; z = y
:
function f1()
# z = y = 1.0
y = 1.0; z = y
@assert y === 1.0
@assert z === 1.0
end
Ok, here’s a version where there’s spooky action at a distance:
function f2()
local y::Int
# ... lots of code here ...
# z = y = 1.0
y = 1.0; z = y
@assert y === 1
@assert z === 1.0
end
Now we get an assertion error because a type annotation on y
has “infected” the type of z
even though the intention of the code is to initialize both y
and z
with 1.0
independently. We expect y
to be forced to Int
but we don’t expect that to affect z
.
With typed globals these days, this gets worse because the type annotation could be anywhere at all, e.g.:
# in some other file:
global y::Int
function f3()
global y
# z = y = 1.0
y = 1.0; z = y
@assert y === 1
@assert z === 1.0
end
Same assertion error but the spooky action at a distance can be arbitrarily distant.
All that being said, I think a better way to solve the issue would have been to parse chained assignment as a single construct instead of as a binary operation with right associativity. Then we could lower z = y = x
like this instead:
tmp = x
y = tmp
z = tmp
z
So the assignements are done independently and the entire expression evaluates to the value of the leftmost assignee. I would consider that a desirable change for (hypothetical) Julia 2.0, and probably not terribly breaking because I don’t think many people will be relying on chained assignment evaluating to the RHS.