Unlike a local scope, a global scope can be split across different files, which makes it a lot harder to tell at a first glance whether a variable assigned inside a block is a new local or an existing global under soft-scope behavior. Nobody wants to write a bunch of top-level loops and try-catches in a new file (like the for-loop part of the docs’ example) then realize another earlier-include
d file possibly written by someone else (like the x=123
part of the docs’ example) already made a bunch of global variables with the same names. That file with global names could also have been the new one, in which case it reverses to nobody wanting to run into a file with a bunch of loops and try-catches that used to assign locals but ended up reassigning your new globals; this might be the worse scenario because it’s much harder for an interactive session to find variables in local scopes than global variables in modules. It’s not reasonable to frequently search an arbitrary number of files to compute a list of allowed names in a module, so hard scope behavior exists to reasonably isolate local scopes from the global scope. Granted, it doesn’t protect you from all name sharing; you can still accidentally reassign global variables in the global scope, it’s just rarer to make even 1 new global than a bunch of new locals in a block.
In the contexts where soft scope deviates from hard scope behavior, you have 1) a running session where you could immediately check for a global variable, and 2) the code is usually contained in one place like the REPL history or a notebook (though you can include
source files with global variables and still run into unexpected behavior). While that helps, I personally do not like the soft scope because it complicates the scope rules and makes moving code to .jl files annoying. However, people really wanted to be able to paste local scope code from files to somewhere piece by piece and look at the variable state throughout; you can’t replicate that by pasting into a let
block in the REPL. Maybe if there was a solid interpreter/debugger mode for arbitrary local scopes, the soft scope wouldn’t have been created to fill Main
with persistent globals, but it’s here to stay in v1. v1.9 onwards, making a throwaway module and setting it as a context helps a lot; I can almost pretend I’m stepping through a let
block.