This was a tough decision that we deliberated over for a very long time and had many conversations about. The old behavior was carefully designed to make the behavior of loops and other scope-introducing constructs the same in global scope or in the body of a function. However, the down side was that to accomplish this, whether an assignment inside of a loop or other non-function scope assigned to a global variable or created a new local variable depended on whether a global binding for that name already existed or not—which is not, in general, a statically predictable property. This also created a distinction between the kind of scope which a top-level loop or other non-function scope-introducing construct created and the kind which functions created. This behavior was widely misunderstood and often complained about when people were trying to wrap their heads around the scoping behavior.
Now, in 0.7/1.0 there is only one kind of scope: functions and loops and other scope constructs are all the same. So that’s much simpler and now whether a variable is local or global is always statically predictable. But the down side is that the same code in a function or on global scope do not behave the same anymore. This trade off is unavoidable given the way local variable are implicitly introduced in Julia—we really explored all the possible options for this. Languages that require you to declare local variables don’t have this problem but then again declaring the occasional global is a lot less difficult than declaring every local.
There is one possible way to recover the old ability to paste code from a function body into the REPL and have it behave the same, which is to automatically wrap the code in a let block to make the behavior like that in a function body. We’ll probably experiment with this in the future and see how well it works. It feels a bit weird to special case the REPL like this but it may be better.