Global scope: the meaning of the first-hit rule

I would like to figure out the following example:

x = 1
for i in 1:3
    x += 1
end
println("x = $(x)")

This fragment pasted into the repl fails, ERROR: UndefVarError: x not defined.

I think the scoping rules are that if a variable is read-from on the first hit, it is assumed to be a global.
If it is written-to on the first hit, it is assumed to be local. That makes sense (I might have been the first to suggest this, in fact). Here the writing may be the incrementation of the variable.

However, wouldn’t one perhaps think of this as the incrementation being equivalent to 1. read from the variable, and 2. increment and write back into the variable? In that case the first hit would have been a “read”, and the loop should have succeeded.

No.

See manual section https://docs.julialang.org/en/latest/manual/variables-and-scoping/#Local-Scope-1.

In particular, note this from manual:

In a local scope, all variables are inherited from its parent global scope block unless:

* an assignment would result in a modified global variable, or
* a variable is specifically marked with the keyword local.
Thus global variables are only inherited for reading, not for writing:

julia> x, y = 1, 2;

julia> function foo()
           x = 2        # assignment introduces a new local
           return x + y # y refers to the global
       end;

julia> foo()
4

julia> x
1
An explicit global is needed to assign to a global variable:

Closer to your example, try this:

x = 1
for i in 1:3
    y = x
    x = y + 1
end
println("x = $(x)")

Here, x is read first, but this errors since x is deemed local because it’s assigned to.

Yes, in fact x += 1 is lowered to x = x + 1.

2 Likes

I think you are referring to the PR that implements the new scope solution discussed here.

If you build julia with the linked branch, this fragment should work. However, the clamor about the 1.0 global scope mostly died down and the PR did not get merged, so your fragment is not expected to work on master.

2 Likes

The clamor about the 1.0 global scope died down because a solution was being proposed/worked on, not because the problem went away (see also e.g. recent post Use of variable in Julia REPL). People understand that a solution takes time, and in the meanwhile softglobalscope can be used as a stopgap solution for the most interactive uses (default in IJulia, use in the REPL should be easier or even default IMO)

2 Likes

That answers my question, thanks. This forum is such a fantastic resource because there are people like you who keep up with what’s going on in the language!

2 Likes

Amen. And the only thing that is preventing a deluge of user confusion is that @stevengj patched the problem for jupyter (which is what most entry users start with). Speaking of which, I have never seen an issue posted on discourse, slack, or anything else about users being confused by the softglobapscope… Whatever logic that uses for globals is clearly intuitive.

For sure. But the last time I asked about this on slack, the response was basically “we decided not to go with those other solutions and are planning to just give better error messages”. Unless that has changed, I suggest to begin the clamor anew.

2 Likes

I think the data also supports alternative explanations, eg that people just got used to the new scoping rules and moved on.

1 Like

Which data?

Questions/discussion on this forum.

Many fields don’t have a culture of posting confusion on public message boards (including economists, as you know). I have to yell at even tech’d up grad students to get them to ask for help publicly. That is especially true of introductory users who just write matlab scripts, and don’t want to come across stupid publicly.

You know selection bias as well as anyone. So by “moved on” there could be a lot of people tried julia, got confused, and decided to wait.

1 Like

Perhaps you misunderstood: I was not pushing a particular interpretation, merely pointing out that that what we observe is compatible with an alternative one (and of course, many others). I agree with you about selection bias, unobservables, etc.

That could of course happen (and, to be pedantic, one could equally well argue that it is happening because of any other language feature/change, since we are truly talking about unobservables).

Arguments about language adaptation, especially counterfactual/hypothetical claims, usually have limited value. Which is why I think it is best to approach this decision from a technical/language design perspective.

Sounds very pragmatic, but here is the issue: this feature is intended 100% for people doing interactive scripts, and for largely introductory programmers. The more time you spent writing packages and submitting PRs to the core julia libraries, the farther you get from understanding those use cases. So then you have a bunch of people discussing technical issues without thinking about the usage scenarios it is intended to fulfill, as the end-users of the feature never have representation in those discussions.

At which point you say something to the effect of “I only care about people willing to contribute to the language and with the inclination and skills to be open-source contributors”. So lets not replay the conversation again and pretend it is just a technical issue. It is a strategic decision about how approachable Julia is meant to be, and whether the intention is to be competitive as an interactive scripting language for low-tech users. If that is not the intention, I want to know about it ASAP because it changes my personal strategy.

I think that we still have consensus that something needs to be done that involves some combination of better error messages, better documentation, the first-hit-rule, soft global scope and possible divergence of REPL and script behavior.

So there is no need to rehash the (at times very heated) discussion about the strategic decisions about how approachable julia is meant to be for non-technical users.

As far as I understood, the issue simply dropped in the personal priority queue of the people who could implement and meaningfully decide on it, and will simmer for some time. We have a reasonable PoC for first-hit-rule and soft global scope, and most people here could contribute to the docs.

I am personally still very partial towards improving the error messages first. My preferred improvement would be that UndefVarError always displays a fully qualified name of the symbol it failed to resolve, i.e. local foo used before initialization or global Main.foo not defined or Captured foo in closure of type Main.##7#8 used before initialization. That would give new users the necessary information (“Oh, julia believed that I wanted a local variable. But I meant the global var. Stupid julia, no wonder that this failed! It is a scoping issue instead of a typo or a mysterious WTF issue.”).

Unfortunately I am not sufficiently acquainted with the relevant sections of code to fix that, nor sufficiently motivated to become sufficiently acquainted with that part of julia. The error printing is not the problem. But the extra info needs to be generated and stashed somewhere between the compiler, lowering, and the interpreter without changing layout or semantics of the exception structs.

PS. I apologize if I somehow sounded annoyed. That was not my intention, and is not how I feel about this. Maybe also bump the PR or issue if you feel that the global scope problem does not get the attention / priority it deserves.

2 Likes