Another possible solution to the global scope debacle

Each module has its own global namespace; that’s an orthogonal issue to this one.

Just to confirm that this would have no impact on closures defined at the global vs. within function scope. So, the following behavior in the current REPL or in jupyter

a = 5
f(x) = a + x
@show f(2)

vs. creating it in a function

function g(a)
  f(x) = a + x
  @show f(2)
end
g(5)

Would have the same behvaior (and performance) in the proposed solution compared to today?

Has anyone considered making the behavior for interactive REPL use optional? The presence of the option could be hinted at in the error message and the user could then choose which behavior to have.

2 Likes

First of all, I want to apologize for referencing the “how” part with the release time way up there - it wasn’t really related to the proposal itself.

I’m definitely in favor of the proposal, it’s a change I feel would make quick Julia prototyping in the REPL much easier when transitioning the code to a standalone script.

@baggepinnen having additional options is almost always a good idea, but in this case adding that option would require somehow keeping that setting consistent and share it with code copied from the REPL - otherwise shared code might not even run the same depending on whether that flag is set by all people involved, if I understand correctly. I feel that that’s more hassle than it’s worth.

I think it would make sense to have a way of opting out of the new behavior, just as there would be a way of opting in. Maybe one could put @hard_scope in startup.jl to get the 1.0 behavior at the REPL, and similarly at the top of any script which uses the 1.0 rules.
Correspondingly, one could put @soft_scope at the top of any script or module where one wants the 1.3 behavior.
Using either of these two pragmas would make the code unambiguous, and thus ensure that one gets no warnings/errors in 1.1 and 1.2.
When copy/pasting code, one should make sure to include the pragma if the code relies on it. (Most code won’t.)

1 Like

I told my wife over breakfast today (she’s a physicist who teaches Matlab at university, not a Julia user yet to my chagrin) about this issue. She was aghast about the local scoping of for in v1.0 :smiley:, but interestingly she had no problem with function scoping. She said, “you guys have to change that… to hell with semver promises - just explain it!”

(minor edits)

6 Likes

I also like this possibility in addition to the proposal. If we want to change the default scope for all variables in a loop, we just could just do local for... and have to type much less. Nice.

2 Likes

Do you trying to politely say that current design is bad? (It seems that many think so but don’t want to hear it :wink: )

If (!) we agree that we want to change current (no bad :stuck_out_tongue: ) behavior we will have another problem.

How to do this change? (and it is big problem)

And it is similar cost-benefit situation as somebody else was saying in this topic.

I am still not sure what is better.


PS.
And there is also other possibility how to understand non defaulting @inbounds , @simd , @fastmath!

Not that default is about performance but that default is about safety. (What is simple - has to be safe)

And we could ask: what is safe of blindly copy-paste code without context into global scope where it could change global variables?


I just have to repeat what I said above: "I am still not sure what is better. "

1 Like

Or we could also have global for which could make easy to teach Julia and still not needed to break 1.x stability promises?

EDIT: It is probably not so good idea so I change it to question.

1 Like

Late to the party here.

making everything outside of a function global, unless it is explicitly declared with local or let

Would variables inside the body of a let block be global? (Presumably, yes, see here.) If so, then IIUC this would nix being able to reliably copy/paste the body of a function into a let block (a common debugging strategy, whether performed manually or via Rebugger). For example, if I did this for a function which had a line first = true (defining a local variable named first), would I henceforth get the equivalent of

julia> first = true
true

julia> first([1,2,3])
ERROR: MethodError: objects of type Bool are not callable
Stacktrace:
 [1] top-level scope at none:0

?

In my own usage, simplifying 3-line REPL demos is not worth messing up systematic strategies; while I recognize the problem this issue is causing, for me personally this kind of change would be a step backwards.

However, being able to say local let ... end does seem like it would solve this problem. Can we keep that option on the table?

17 Likes

Can I just put in a vote for the current 1.0 behavior?! I think people will eventually get used to it, just like 1 based indexing and other small issues that come up when learning a new language. I also think it is the best design overall.

Keeping the current behavior also has the big advantage of not needing a complex transition or breaking SemVer!

10 Likes

I like the idea of the local for ... end loop and I also think that keeping 1.0-style scope would be better. So how about just adding

global for i =1:100:

end

?

8 Likes

I agree, with the only concession to the REPLers, that they can switch it in the REPL optionally.

1 Like

Perhaps there’s no harm in allowing local/global for/let/etc to override defaults anywhere. Maybe allowing global let/for inside a function makes very bad things too easy to do, in which case it may be disallowed and then the global in global let/for would always mean nothing in the end (with this thread’s proposal), but it doesn’t do harm anyway: when in doubt, be explicit. But it could be more consistent to have it, and allow for a less invasive transition to the proposal in v1.3 (where qualifying any let/for block outside of a function could be compulsory instead of qualifying each var in them)

being able to say local let ... end does seem like it would solve this problem. Can we keep that option on the table?

ok by me
I like the flexibility that local as modifier allows us, presuming it is a valid modifier for more than let blocks. If we have it, in keeping with the principles emerging as desireable, we should embrace its applicability to any otherwise global syntactic context: local structs, local modules probably have good uses not well addressed otherwise.

Is there an ambiguity issue for nested for loops in the proposed solution? E.g.

x = "foo"

for i = 1:4  
    local x = "bar"
 
    for j = 1:4 
        x = "ugh"  # which x gets reassigned?
    end
end

@show x # ???

I tested this in 1.0 replacing the for loops with if true, and the result of @show x is "foo". That’s what I’d want. And if the proposal just makes for scoping the same as if scoping, that means no ambiguity. But I think then the scoping rules should be explained in terms of local, outer, and global, not just local and global.

2 Likes

I like the solution outlined by Jeff in the original post, along with the rollout plan that Stefan suggested. I much prefer the proposed behavior over what happens at present.

Yet another thread from someone confused by the new global scope behavior: Bug with Julia 1.0.1. The rate of discourse threads about this is about one per day—at least several per week. There seems to be a roughly even split between old Julia users who are confused because they’re upgrading and this has changed from 0.6 and new users who have no preconceptions except what they expect from other languages and their own intuitions. To me this is all clear enough evidence that something needs to be done and leaving the behavior as-is would be an ongoing problem and a bad first experience with the language.

26 Likes

This example’s behavior wouldn’t be affected by this since the x in the for loop has already been annotated as being local, which means that foo in the for loops cannot overwrite the outer x which is global. Or in other words, the innermost x currently assigns to the outer local x not the global x and that would not change.

1 Like

Very interesting discussion.

What I miss is the background story and the trade offs the designers considered.
For instance, why the current behavior chosen?
The people who designed it had something in mind (Performance [@StefanKarpinski mentioned this is not an issue] , Clarity of code, Less edge cases, etc…).

Once all those considerations are on the table we’ll ask ourselves what is gained and lost by changing the design again.

As it seems the current discussion is mostly about habits of the user of Julia while there might be other significant factors in this design choice.

For me, if from many factors the current situation is the right one (Given how well deigned Julia is, I give @jeff.bezanson and the rest of the team the credit they made this change into 1.0 as it made a lot of sense from design perspective) it is OK to leave it as is and expect people who want to use Julia to learn how to use it.

I’m pretty sure that for REPL in demonstration cases someone will come with a clever trick (My bets on @tim.holy :slight_smile:) to make the demonstrator get away with explaining the scope.

3 Likes