-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Shadowing in closures in let
blocks influences bindings in neighboring functions
#56002
Comments
let
blocks influences bindings in neighboring functions
I don't think this is a bug. Simpler example: julia> let
function b()
b = 1
return b
end
b()
b
end
1 The Perhaps a candidate for strict mode? Xref #54903. |
I'm not sure I understand what you're trying to say, this is not the usual terminology. All non- I also want to point out that as a package author, there is pretty much nothing I can do to guard against this. The original MWE (involving Nit: Regardless of this being a bug or not, I picked the |
I don't know what was the intention, but agree that this is a footgun. Fixing it seems potentially breaking though. As in, the PR to fix it would have to get a Pkgeval run.
Because this seems like a semantics/language design issue, not just an implementation bug. |
I guess that's true 🤔 The underlying issue on my end would also be solved if |
This comment has been minimized.
This comment has been minimized.
I think this is more or less the same issue as #14948, although a slightly different manifestation ofc. |
From the POV of dev-UX, this is an antipattern, since it subtly changes the meaning of code inside. IMO it'd be better to have individual testsets more isolated; introducing a new global scope would be fine. That's also what TestItems.jl does (I don't use it, but lots of people seem to enjoy the features it has).
Thank you, that indeed seems related! In particular the discourse thread at the end is interesting, because this issue is an example where moving code around definitely changes what the code means. |
Ah, sorry, that option didn't come to mind. A module per testset sounds nice. |
Unfortunately it's also breaking for the Test stdlib, because then you don't get the implicit namespace inheritance you get from the current design. Accessing variables from an outer scope would require explicit namespace traversal upwards, which is not required today. Hence the issue being about lowering this edge case :) |
Amusingly, one can use this to inspect variables inside other functions as well: julia> let
function a()
c = 2
b()
end
function b()
b = 1
return b
end
function c()
5
end
@show a()
@show c
end
a() = 1
c = 2
2 |
There's nothing really special about either let or closures — this is local scope using the same name and reassigning to it. I'm not sure what a "fix" would really even look like. Maybe local-scope named functions could use a local const for their names (if/when we have it: #5148)? |
I think that's a good idea, yeah. I really hope noone relies on this rebinding behavior for local functions in particular... |
Imho the problem here is the same old problem that definition and reassignment use the same |
The surprise is that function declarations which appear inside let
function a()
b()
end
function b()
local b = 1
return b
end
@show a()
@show a()
end However, being pragmatic, it's a worthwhile compromise in order to support classics such as mimicry of objects using lambdas: function make_account(initial_amount)
x = initial_amount
function deposit!(arg)
x = x + arg;
nothing
end
function withdraw!(arg)
if x < arg
error("insufficient balance")
else
x = x - arg;
end
nothing
end
function f(op::Symbol, arg)
if op === :add
deposit!(arg)
elseif op === :sub
withdraw!(arg)
elseif op === :ret
x
else
error("unknown operation")
end
end
f
end
withdraw!(account, amount) = account(:sub, amount)
deposit!(account, amount) = account(:add, amount)
get_balance(account) = account(:ret, nothing) In general, many of what appear to be quirks on the surface hark back to Scheme. There, |
While this issue as a whole is certainly a case of "lexical scope is working as intended", I don't love the outcome in this case and I feel it would be great to make it an error.
@mbauman I like this solution a lot. For outer functions, the |
Sorry, maybe I'm misunderstanding something - what expression exactly should error? Note that this was uncovered through a macro expansion inside of an |
MWE:
As far as I can tell, what's going on is that
a()
andb()
end up sharing the closed-overb
inb()
; either that, or the assignment inb()
ends up overwriting the binding tob
in thelet
block, after which the call tob()
ina()
fails due to the new object not being a function.I'd expect this to behave the same as without the
let
block, that is, just printa() = 1
twice. This was found by a user of Supposition.jl, downstream issue is Seelengrab/Supposition.jl#54. To be clear, the shadowing inb()
is fine, it's just surprising/unexpected/undesirable to have that shadowing influence what happens ina()
(which shouldn't care about the shadowing inb
, and just call the function).The text was updated successfully, but these errors were encountered: