-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
rules for shadowed bindings? #1357
Comments
Maybe it just needs to be a const? |
Different namespace. Base.im is const.
|
t.jl
t2.jl
I am confused. There is a non-const and const version of im both imported from Base but you can only use the const version of im by typing Base.im and trying to assign to Base.im reports that is does not exist? |
julia> import Base.im julia> im = 5 This behavior is very confusing. Depending on how you import the value, you may or may not get a warning and you are still allowed to effectively overwrite a value that is supposed to be a constant. |
You are not overwriting or changing the constant. You are creating a new module-local binding for im. It is a separate variable in a different namespace. Being able to do this is the whole point of namespaces. Base.im still exists with its usual value. If the sequence I decided only to give the warning for code that directly contradicts itself, such as the explicit import |
On one line "im" refers to the constant Base.im and on the next line to the yet to exist Main.im. This is confusing regardless of the story told to explain why it happens. When a variable is imported without any prefix, typing that variable name without any prefix, should refer to the variable that was imported, not a new binding in the current namespace. I hardly think allowing users to silently shadow variables is the whole point of name spaces. It may be very annoying, but that is a sign of a larger problem. Using the import Module.* syntax destroys namespace separation and should be used sparingly. If just doing import Base.* causes namespace hell, then there is too much exported in Base. I don't think the ability to silently redefine functions and function methods is any better, but maybe this discussion should be taken somewhere else. |
So you think imported bindings should take precedence over bindings in the current module? I.e. Most people expect to have simple things like +, -, im, etc. available by default without a lot of import statements. It's true that you might want to be careful with import Module._, but it does not destroy anything. Other modules remain fully untouched, and nothing in another module can take control over how your identifiers resolve. For example |
I have to agree that I find the current behavior kind of confusing. I think the issue for me is that writing julia> im = "foo"
"foo"
julia> im
"foo" That's really bizarre. The |
You are right about scoping. I wasn't thinking of importing as just creating new bindings in the local module for values in another module, but as pulling the bindings of the other module into the current one, that is creating a shorthand for using the bindings in the imported module. I didn't realize that whether the bindings in the imported module were const or not didn't matter at all or that you can use a module without importing it. This is why I felt the bindings created by the import should work the same way as the bindings in the imported module. I am troubled by the instability of the meaning of im and other bindings, but am not sure what the best solution is. I appreciate your patience and feedback. |
Yes, the const-ness is unrelated. If you are allowed to make a new variable shadowing an imported one, it has its own const status (since it is a new variable). If The point about the warning I can understand; maybe we should warn about all shadowed bindings. It helps to consider a name other than |
I'm surprised nobody has used this example yet, which is the only real bad case I can think of:
The problem here is that the binding for But, the assumption is you have some awareness of what variables you're introducing (assigning to) in your module. After all, it can be determined by looking locally at just your module. That is a good property to have, and the rest is slightly less important. |
I get the reasoning behind the current behavior (you're introducing a new binding, not changing the imported one) but I still find it kind of unsettling, but I'll have to sleep on it a bit. |
Part of the mismatch here, it seems, is that technical users might expect certain bindings ( Of course, not all users fall into domains where that makes sense. I've used |
I think part of the issue is that intuitively when I write |
I think that import should be doing something more along the lines of what I thought it was doing. I find this odd: julia> module Foo
a = 3
b = 45
export b
end
julia> Foo.a
3
julia> Foo.b = 7
type Module has no field b Why doesn't a need to be exported for me to use it in Main, and why can't I assign a new value to b even though it is exported? Okay, so I can import b into Main and create a new binding, but that typically is not how one wants global variables to behave. If things worked as I thought, then if a one wanted to share a value, but not have it change, he would make it a const. If a variable is supposed to be totally private, it would just need to not be exported. |
The motivation behind not allowing assignment to bindings from outside a module was to have some level of "closure" and thereby leave more room for potential static analysis of modules. I.e. once a module is defined, its bindings are essentially read-only. There are some exceptions, of course, since you can expose functions that mutate the state: julia> module Foo
import Base.*
export x, inc_x
x = 1
function inc_x()
global x
x += 1
end
end
julia> import Foo.*
julia> x
1
julia> inc_x()
2
julia> x
2
julia> Foo.x
2 I'm not sure how I feel about the fact that Back to the bit about not allowing assignment to module bindings, the point of that choice is that as long as a module binding isn't captured in any closure, you can be sure that it won't change in the future, which can be helpful to the compiler. But personally, I'd rather make global bindings default to being type-const [#964] but allow assigning new values. That would be more helpful to the compiler and feel less restrictive. |
You should not be worried about what "exists in some actual sense", but about how concrete sequences of statements behave. And it is not surprising for x to equal 5 after writing Everybody seems to agree that it is nice to be able to access all variables if you prefix them The exception you give is not an exception at all; the assignment takes place in the same module. And if a variable's value is changing and you import that variable, you will see the changes. I have no idea what's spooky about that. What other behavior would make sense? If imported |
Telling people what to worry about doesn't really help. Having I think the root of the "spooky action" issue is a difference in mental models for what import does. The current model is that imported bindings are actually shared between modules: after writing There's also this oddity which maybe deserves an issue of its own. After doing the above, I tried the following to see if I could affect the binding julia> function inc_x2()
global x
x += 1
end
julia> inc_x2()
in inc_x2: x not defined
in inc_x2 at none:3 I could not, but this seems like not what ought to happen in any case. |
What gives me trouble here is the premise that somebody has written Maybe we could say the intended meaning of The behavior of If you have a mutable global variable, you definitely don't want to copy it by default. The var's behavior is dictated by its owner module, so if the owner says it changes over time then that's what you get. Without knowing the detailed semantics of the variable, you don't know what it means to "snapshot" it. |
Wanting im to be a constant and having written |
There is not one example here of having a value not be what you thought it was. I understand the argument for giving an error, but the current design does not lead to a variable getting an inscrutable value. After writing If we give an error, then we mandate the following workflow: import what you need, set up some definitions, then see what errors you get and fix up all your imports and names to allow you to use the names you want. That third step is just unnecessary. |
Oh, there's also a fourth step: when somebody else's module changes, see what new errors you get and do the busy work of avoiding clashes in your names. That kind of thing is not supposed to happen. |
Someone could try using im expecting it to be equal to Base.im and have it be 5. Just because code exists within a module someone is adding code to does not mean he knows what all of the assignments are in the module. I don't see what the big issue is with fixing name clashes in an import. Why is it expected that there should be name clashes and this is a good way to program? When someone writes code that does not make sense, it is normal to get errors and to have to fix them. Yes, this may mean changing the name of a variable or not importing a variable you are not using. You'd have to rename it anyway if you actually want to use these two different variables at the same time and if you aren't using the variable, why do you need to import it? |
Adding code to a module you're not familiar with is always going to be a problem. I could still do the following
Exact same problem. You could add code to this module expecting |
Assuming people follow reasonably standard conventions for the location of import statements, I think import statements are more likely to be noticed than assignment statements. |
Our latest thinking is that constructs involving the same word ( Saying we are "expecting name clashes" is putting the wrong spin on it. This kind of issue is unavoidable. You can have a global Sure I'm all for errors for code that doesn't make sense, but it's a problem if you can't know whether your code makes sense without knowing the full list of bound names in the current context. Telling people to individually import everything is absurdly pedantic. Sure we can make Example from python:
Example from c++:
C++ allows the second assignment to |
Examples of the proposed usage: module Foo
using Base
using Other
import Bar.baz
import Bam.qux
... This would cause |
Branch merged. |
im is not a reserved word.
julia> im = 7
7
julia> 3im
21
The text was updated successfully, but these errors were encountered: