New scope solution

Forgive me if I’m missing something, but couldn’t we perhaps have an even simpler heuristic: variables in non-function scopes are global when they exist outside of a function scope?

In other words: are there examples of for or let blocks evaluated in a module scope where we definitely would want the variables in those blocks to be local? Conversely, is there a case when they exist within a function body where we would want variables to (automatically) be considered global?

Since top-level REPL evaluation occurs in a module scope, this should (if I’m understanding everything correctly) bring us back to pre-v0.7 behavior for for loops. While this would technically be a breaking change, I suspect it wouldn’t impact very much actual application or package code (again, assuming that for or let blocks are rarely found outside of a function body in packages or applications).

You could also apply this (or @jeff.bezanson’s and @StefanKarpinski’s) heuristic if for is “declared” as global for so we:

  1. don’t need to make breaking change!
  2. will have better possibility to code with “explicit is better than implicit” paradigm with for without global keyword.
  3. have quite simple method (i.e. “add global keyword”) to make teaching easier.

Alternatively we could do syntactic “hack” (*) similar to @goto and @label:

@auto_global for 

this give us possibility to have other heuristics in the future.

In REPL @autoglobal on and @autoglobal off could change heuristics globally (although here we have to consider danger of creating dialects).

(*) - question what language construct @goto and @label is I could probably ask in future in another topic. :wink:

What do you mean by that? Do you mean every variable declared in a global for for is automatically (and implicitly) declared global?

I meant that without global it is stay as it is. With global it apply heuristic. Which one is open question (although I slightly prefer @jballanc’s as simpler).

I’m not a fan of what I just proposed, it was just what I understood. I don’t think global for with implicit globals is a good idea because it will leak variable declarations into the containing scope.

Forgive me if I’m wrong, but I thought for and while are syntactic sugar for let and conditional @goto (plus some iterate calls in the case of for); and if, else, etc are syntactic sugar for conditional @goto.

For that reason, the rule needs to work post-lowering and be clear on this lower level, as well as imply a sensible high-level rule (at the very least, the rule should be invariant under “partial lowering”, i.e. replacing high-level constructs by their low-level targets). Since the rule should be invariant under rearrangement of basic blocks, the second example follows. Or did I get this wrong?

In principle, your rule is on the control flow graph, not AST (that’s why it is more complicated, and also why it should be accompanied with tools to view the CFG; current tools to view the AST are not helpful anymore to debug scoping issues). So it is not syntactic, but close enough not to matter outside of corner cases.

PS.

question what language construct @goto and @label is I could probably ask in future in another topic. :wink:

Goto does what’s written on the label :wink: Sorry for the pun. A label labels a position in your code (so it does nothing). Goto goes (jumps) to a position; you need to label the position in order to tell goto where to jump to. This is super useful for e.g. breaking out of nested loops, and imho far cleaner and more readable than pulling along a bool (typical found) and doing multiple found && break and a final if found ... else end. Also, it is the low-level construct that loops are syntactic sugar for.

Can you please clarify for which constructs and contexts this would apply?

Eg

  1. for, while, try-catch-finally, let, comprehensions, broadcast-fusing,
  2. only for the REPL? or all module/baremodule?

are there examples of for or let blocks evaluated in a module scope where we definitely would want the variables in those blocks to be local?

Yes, this happens frequently when you copy/paste method bodies to the REPL and wrap them in a let. For example, in a function I can use first as a variable without conflicting with the function named first; if first is local to the let, then there is also no conflict and my copy/paste debugging works. If in contrast first is global, I have to change the name of that variable before I can debug in this fashion.

(@StefanKarpinski, I’ve not yet had time to really sit down and think about this carefully; I will try to do so soon, though I suspect you’ll get all the feedback you need from others. No matter what, kudos for coming up with devious solutions!)

2 Likes

2 posts were split to a new topic: Side discussion on gotos

A post was merged into an existing topic: Side discussion on gotos

This was precisely the solution previously proposed here:

All scope-introducing constructs that occur outside of a function body.

  • only for the REPL? or all module / baremodule ?

Both. One of the goals is not to have different behaviors in different places. Since that inevitably leads to a series of questions about “why does this work in the REPL but not in my script/module.” In particular, since “scripts” (aka programs) operate in Main, which set of rules should apply to them? Are they interactive or non-interactive?

7 Likes

:100:

8 Likes

Ok, yeah so I did miss something…apologies! In my defense, it’s been a bit difficult as a bystander to try and follow all the proposed solutions and counter-examples. Glad we have some individuals focusing so much of their attention on this issue. From the multiple issues/threads, it’s obviously something that’s hit a nerve and should be carefully considered…so thanks!

3 Likes

So, this has me wondering how much of the trouble around this issue and proposed solutions has to do with scoping, and how much has to do with shadowing? I wonder if it’s worth considering these as separate issues? Maybe we turn Julia into a LISP-2 in v2.0? (…ok, that last one’s a joke :wink: )

2 Likes

This transformation must operate very early on AST, before lowering. For technical reasons, it has to since it has to come before closure conversion. But more significantly, it would also be very broken if the behavior of such a feature depended on details of lowering; lowering is an implementation detail, not a feature of the language.

2 Likes

How are these different? If a local name shadows a global one, doesn’t that mean that it has local scope? Note that talking about this issue being about scope is a bit misleading: no scopes are changed on any of the proposals. The only question is about automatic inference of local and global annotations when x = is seen in various contexts.

(Also, Jeff and I both found the Lisp-2 joke very amusing :slight_smile: )

2 Likes

Only allowing the introduction of new variables with either a let (more or less equivalent to cl:let) or a global (= cl:defparameter) would also solve this problem, both technically and then socially (everyone would then beg the core devs to return to the 1.0 semantics, and no sane person would bring up the issue again for fear of causing something similar). :wink:

7 Likes

Does this include for loop variables which are not used inside the loop (or a comprehension)?

println("Wake up")
for i in 1:3
    println("5 more seconds")
    sleep(5)
    println("Wake up")
end 
println("Fine!")

No, loop variables are always local.

4 Likes