A binding is an association / a relationship / a fact that a name refers to an object
A name is merely a name (some other people also call it a “variable”)
So what does the operation y = 7 + y do?
It changes the old binding y@_4 = 5.0 to the new binding y@_4 = 12.0.
But in both bindings, the name is y@_4 at both places.
I don’t think the existing doc reads coherent—y itself is not a binding; and y is not changed
My intuition suggests (do not constitute an advice):
Had best not reuse a name from an argument. Do not do the y = somethingElse above, since y is already a name of an argument.
If intend to, had best add a let:
Instead of doing
function f(x, y)
x[1] = 43
y = y * 1.0
y = y * 1.0
() -> y
end
, had best do
function f(x, y)
let y = y
x[1] = 43
y = y * 1.0
y = y * 1.0
() -> y
end
end
I have no idea which one has better performance. But the latter one looks a lot neater.
The former has y@_7::Union{Int64, Core.Box},
whereas the latter only has y@_5::Core.Box.
I’m not sure it makes sense to mix the lowered code with the semantics of julia code in this way. A lot of transformations are done on the way to native code. In particular, one intermediate representation is SSA (Static Single Assignment), the one you see with @code_typed. Then «bindings» are something completely different.
I see it as when you call a function, you pass it objects, not names.
So, in this example, the function initially bind the name y to the object with which it is called, and then it rebinds it to a totally new object.
There isn’t anything wrong in doing that..
I’m going to hazard a guess that you’re thinking of the Performance Tips section on captured variables, which you commented in another thread yesterday. That issue only applies to captured variables like () -> y, it’s otherwise fine and typical to reassign method arguments. Don’t exaggerate contextual implementation limitations, which can change, to unidiomatic code practices.
y@_4 is an implementation detail of code lowering, not a name/binding/variable on the language level that the Manual describes.
That would be a reasonable colloquial usage, but another reasonable usage is the name that has been bound to a value. For example, we also call the materials for the binding of a book its binding. English words are ambiguous by nature, it’s futile to apply the same rigor to them as a programming language specification. You have to consider the context as well, and “the binding (“name”) y” clearly considers binding to be synonymous with name in Julia. I would prefer to describe reassignments without involving any form of the word “bind”, but it is what it is.
No. Can you tell the difference between declaration, read, write?
function f1()
# This function only declares about the name `x`
# There is no read, nor write (assignment)
local x
local x
end;
@assert isnothing(f1())
function f2() # this function reads `x`
x
end
function f3() # this function binds `x` to `3`
local x
x = 3
end
If the_English_word_binding === the English_word_name, then why do you abuse the word binding?
I fail to understand your idea. It really pass the name, see the x[1] = 42 usage.
I didn’t mean it conducts a wrong behavior, I was talking about his inaccurate language.
I fail to get your meaning. Could you elaborate the full idea?
I use @code_warntype because it teaches me how to understand the behavior of my code.
I don’t know if there is any substitute.
Okay, I believe you if you are a native speaker.
But here is what I found
I think you’re right that a name is only one part of a binding, and that the quoted sentence in the manual could be made a bit more precise.
On the other hand, my impression is that using just the name to refer to a binding is pretty common, the same way that we (and maybe you as well?) would use y to refer to both a variable and the value it refers to.
This, I don’t get. To pass the name, you should use a String or a Symbol. The thing that is passed to the function is a value/object, which is then bound to the name x.
As for reusing the name y, you may have a point, but mainly for performance. Doing something like y = 1.0 * y can easily change the type of y, which, at least in the past, used to have negative performance implications.
I can’t elaborate too much, but the compiler is allowed to make a number of changes to your code, as long as it produces the same output. Looking at compiler output might not tell you what your code ‘really’ means.
Don’t learn semantics from an LLM, and don’t forget that the same English word can have multiple meanings. Again, this is why I would’ve preferred not to use any form of the word “bind” to describe names and reassignments.
You’re right. My language was vague. I withdrew that. It shares the underlying object. An outer name cannot be seen within a function in case like this
x = [43]
f(x, y) # Then from within the body of `f`, the outer `x` cannot be seen
I dislike it either. Because the past participle of bind is bound. And the phrase bound to has other meanings… I feel super strange to read “an y is bound to 7”, when I started to learn julia. Until later I realized that “bound” is the past participle of “bind”.
This is convincing,
The only thing I’m worrying about is:
while I write x = some_object in my text editor, I hope that julia will never ever stealthily lower my code to some code like Core.set_box_content!(x, some_object). This really changes my intention (from an assignment to a mutation!), which would be very dishonest. But the upside is that I receive the final behavior as expected after all.
import Random.seed! as seed!
N::Int = 999
function gen(cnt)
if cnt % 2 == 1
seed!(1)
return rand(Int128, N, N)
else
seed!(3)
return rand(Int128, N, N)
end
end;
function check(s)
if s == gen(0)
println(0)
elseif s == gen(1)
println(1)
else # I cannot see this in practice
println("\tEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE")
end
end;
function test(m)
for c = 1:10000000
for j = 1:14
Threads.@spawn check(m)
end
println("c=$c")
m .= gen(c) # Core.setfield!(m, :contents, %51)
end
end
test(gen(0))
If you write m .= gen(c), then you can observe “EEEEEEEEEEEEEEEEEEEEEEEEE”
But if you write m = gen(c), then you cannot observe “EEEEEEEEEEEEEEEEEEEEEEE”.
That’s not how Core.Box works, and that distinction between assignment and mutation doesn’t make sense. Mutation of Julia objects implemented on the Julia level involves assignment, and whether an assignment mutates an internal Julia object is an implementation detail. Again, be careful to not mix up implementation with language semantics; a variable is still a variable whether it’s internally implemented by Core.Box, a spot on a call stack, or whatever other structure on the Julia or C level.
Hi @WalterMadelim, speaking as a moderator here.
The Julia documentation is written in natural language (English), as opposed to a formal programming language. Therefore, it can never be 100% unambiguous, and some terms may be used in ways that differ from what you expect. That is especially true if you’re a non-native English speaker (I am too).
However, I suggest you ask yourself whether these minor nitpicks deserve a new topic every time. While we of course encourage active forum participation, your rate of creating new topics over reading existing topics is very high. For example, all the other people in this conversation create new topics at a rate that is anywhere from 2 to 20 times lower than yours. Discussions like this one, on concepts that you already understand, take community time away from other users that could genuinely benefit from some help. Please keep that in mind.
I am expecting that: At any time on any threads, when anyone read x, they should either read [0, 0, 0] or [1, 1, 1] (The 2-point Bernoulli distribution).
while when I write mutation like
x = [0, 0, 0]
set_content!(x, [1, 1, 1])
At an arbitrary time, I am prepared to receive an undesired [0, 1, 1]-like intermediate state.
I don’t expect julia to alter my intention. i.e., an assignment is merely an assignment, which cannot allow intermediate states to happen.
If you explicitly perform mutation instead of simple assignment on an object you may observe behavior that is visibly different (as in your example test).
Are you saying that you are concerned that if the compiler optimizes an assignment to a mutation, you may also observe this?
But that would mean that optimization was invalid. My understanding is that the compiler cannot make these changes willy-nilly, but must prove that they are equivalent.
If I wrote set_content!(x, [1, 1, 1]) on my text editor, then it means I intend to do so.
So I’m okay with receiving an intermediate state like [1, 1, 0].
And in my practical test above I do observed this.
But in practice, since this is unsafe, I will have to carry out locks, i.e. using @lock in every threads, which is inconvenient.
It cannot do this. This is altering the user’s intention, which is invalid.
Assignment is in no way the same concept as mutation.
The former is changing a continuous block of memory, while the latter is just a re-associating action.
e.g.
x = [0, 0, 0]
x = [1, 1, 1]
The expected action for the second assignment is:
create the object [1, 1, 1] in the memory
let the name x refer to the established object [1,1,1]. (But before this happens, the x was referring to [0, 0, 0])
Unfortunately, the test code in my #11 post serves only as a necessary condition—it is not sufficient. By observing the results, I can only infer that julia currently is not violating my intention, which is agreeable. But I can not prove that julia is doing everything all right.
Unfortunately I can’t. This is somehow like to ask one person to come up with an instance to prove NP = P. (a nonserious statement)
You’re talking about a kind of data race, an implementation detail. Languages don’t specify how assignments are implemented, so there are no guarantees about data races. Use proper locks and atomic operations on a variable shared by multiple tasks, or refactor around sharing it to begin with.
I see. But how can you explain that I cannot observe the unexpected behavior in my #11 post?
Can you come up with an counterexample to give a definite answer?
What do you mean by “refactor around”?
In large possibility I should can observe this.
But as my test in #11 post, I do not observe.
I can’t, and I’m not going to bother digging into how any particular version of Julia implements that example. The point is that the language doesn’t specify what happens, and any demonstration of undefined behavior doesn’t describe the language.
Rewriting the program for tasks to not share a variable (or element, field), yet reach the same result.