I’d like to make sure I understand “variable binding”: what happens when
using x =...
in different scope constructs, particularly when the name
x
remains the same.
I picked examples that seemed more unintuitive for me; I could have split
them in many posts, but they are very inter-connected.
So here is an exercise for you to see where I’m getting it wrong
EDIT: if you also explain in a line WHY it’s wrong, it will be awesome!
But a single person not have to answer all of them: just please mark
which you are answering, and I will edit to remove the questions from to correct from here.
here.
I hope will be helpful for other users too.
Thanks!
EDIT: NOTE TO THE READER
The strike-outs below reflect corrections in my own understanding, most of them due to the discussions with @yuyichao (thanks again! ) in the replies below, to the extent I understood him. Where I still have a doubt, I’ll leave a (?)
.
In a new module, or fresh REPL:
1. Assignment
x=1
Creates a “variable(name) binding”: a new name (or variable) name-location (associated with associated with /pointing to / bound to value (or object)
“x”) , pointing to a new value-location (storing 1
)1
.
CML: ( by “CML” I will mean At computer memory level, at “RUN-TIME” (after compilation) ) :
in case this instruction is inserted as is, by itself, at REPL, then some location in memory will be occupied by the (binary repr. of) value 1
, linked through the address of that location to the name “x”.
However, in other contexts (for ex: not at REPL, or when this instruction is surrounded by other instructions in a block…), due to complex compiling optimizations, the above statement about CML may not be true. (Read replies in the thread to understand why).
For this reason, and because I know too little about compilation, I will leave out any specific interpretations at CML in the next parts, except for a comment on it.
2. Re-assignment
x=2
? Keeps the same binding (name-location and value-location), just
overwrites the value-location with 2
Re-binds the same name/variable “x” to value 2.
CML: cannot say whether the original value location (storing 1
) is or not overwritten with value 2
: if x=1
was enter
-ed separately, then followed by x=2
, then it might (?), but if both entered as x=1; x=2
then an “original” value location storing 1
may not be produced at all !
Bottom line: it’s compiler’s business, as long as it produces the final result, compliant with the semantics-level interpretation I wrote above this paragraph.
3. Argument passing
After reading this
https://docs.julialang.org/en/v1.0/manual/functions/#Argument-Passing-Behavior-1
f(a) = print(a)
f(x)
A local var. binding: new name a
name-location (associated with string seen only from within body of
“a”), but pointing to the same value-location of x
as beforef
, but referring to the “same” value/object as of x
(“pass by sharing”)
What “same” means here:
- in case the value is immutable (like in our case, or say a tupple), then it just means “equal” value, and the semantics is equivalent to “pass by copy”.
- in case the value is mutable (like if
x=[0 0]
), then it means botha
andx
share the same “reference” to the value/object . This “reference” doesn’t have to be simply an address of the memory location holding the value: is like a common “handle” that just guarantees that modifications to value ofa
within body off
will reflect inx
too (say witha[1]=1
)
CML:
- with immutable values, compiler is free to either copy the value of
x
to new mem. location or keep it in same – we don’t know and should not care, because neither violates the semantics of the language. - with mutable value/object - I think the compiler better keeps the object in same location in memory, and “reference” simply be the memory address of that location (?) – but hey, as long as the specification above holds, it is also allowed to keep multiple copies of same object, cross-linked with direct or indirect pointers/references, eventually linked to that common “reference” in the semantics.
4. Local assignment
g(a) = (x=a; x = 3; print(x))
g(x)
Initially happens same as in #3. above.
Then, with x=a
in the body of g
, another local name-binding is
produced: new name x
name-location (but with same name string “x”), and , new bound to same value as before (
value-location as well, though holding2
).
Because of that, next x=3
does not change the outer (global) x
, but just re-binds local x
to value 3.
5. global vs local within a function’s local scope block
The interpretation/question is in comments:
function h()
global x=2 # ?? same thing as in 2. above
x = 5 # ?? strikeout(same thing as in 4. above)
function j()
x=10 # ?? strikeout(keeps the same variable binding, just overwrites the value)
end
end
h()
# ?? strikeout(so global x=2 now)
EDIT: my interpretation of line x=5
was wrong: x
was made global by line above, thus stays so within current
block. Hence comment for x=10
is also wrong, and so is the last comment:
thus, finally, after h()
call, x
is 5.
EDIT: comment for x=10
is not wrong only b/c of scope, but just overwrites the value : see above CML notes…
6. in let
, nested in global vs in local scope block
x=2
let x=1
end
function k()
x = 0
let x=1
print(x)
end
print(x)
end
k()
# so global x=2 still
In both cases of let
, new var binding is formed, local to let
: new name
name-location (associated with “x”), and new value-location (with 1). with value 1
.
Hence k()
prints 1 then 0 , while global x
is still 0
.2
7. inside while
x=2
while true
# print(x)
x=x
print(x)
break
end
Will not print anything, even if uncomment Ist print(x)
, but just
give:
ERROR: UndefVarError: x not defined
because:
x=...
is detected first, syntactically, and makes x
local( creating
new name name-location, and (?) allocating an empty value-location for it ) but not bound to any value).
Then evaluation happens, top-down, but Julia does not have any value to
replace right-hand side of x=x
or in print(x)
, b/c making x
local has applied to the RHS of x=x
as well, and thus bans it from using the global x
value.
See also this discourse post.
8. in let
: next to it vs inside its body
x=2
let
x=x # gives error just like 7. above.
print(x)
end
let x=x # works
print(x)
end
In the last let
, in contrast to the first let
and to while
loop in 7. above, let
also introduces a local scope, however if assignments are written right next to let
:
then the right-side of assignment x=… is evaluated first (thus getting the value from the outer scope), then it creates new variable binding: name-location, and value-location, to store that 2 value.
then the names in the LHS of assignments are made local, while the names in the RHS are made global. At evaluation, the right-side of assignment x=..
is evaluated in the outside scope, hence x
in the local scope get’s bound to 2
(2 is written in its value-location)
See also
https://docs.julialang.org/en/v1.0/manual/variables-and-scoping/#Let-Blocks-1
, below the Ist code block.
EDITS: scopes of names in both LHS and RHS of the assignments are determined syntactically( at “compile” time), before the execution/run time.
CML: The (2 is written in its value-location) : because is not true in general (i.e, with any such assignment, in any let block) – as explained in 1., 2., 3. …