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 bothaandxshare 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 ofawithin body offwill reflect inxtoo (say witha[1]=1)
CML:
- with immutable values, compiler is free to either copy the value of
xto 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. …