Basically for the same reason you would prefer RAII to new/delete. You can easily forget to change the value of a global variable back to its old value, whereas dynamic variable bindings automatically revert to their old values after you leave the scope of their declarations.
The problem with local variables is that you can then not call a function that uses them unless you define such variables. One shouldn't be forced to know of such variables before calling such a function, since then they'd hardly be different from regular arguments.
The difference with statically-scoped global variables is that you can't set them to different things in different threads, and you have to be careful to set it back to it's original value after you called the code you wanted.
In other words, it's the same difference as using environment variables vs configuration files for executables.
If I ever saw anyone I worked with trying to do something like "setting a global variable to different things in different threads", I would have very strong words for them. If they didn't listen, I'd have even stronger words for their manager: "Fire this bozo immediately". In what sane world is that considered a good idea? Is there any way in which doing so via "dynamic scoping" makes it any less of a horrible idea?
I'm having trouble making sense of your first paragraph. Part of the point of local variables is that they're local. It doesn't matter whether the function you're calling uses them or not. If it uses something with the same name, it's their own copy, and you don't care whether they do or don't. If they want yours, you pass it in. What am I missing?
> If I ever saw anyone I worked with trying to do something like "setting a global variable to different things in different threads", I would have very strong words for them.
Because global variables aren't used the same between the two types of languages. In lexically-scoped languages, it's bad practice overall to use global variables for anything except constants. In dynamically-scoped languages, they're used as an environment just like how environment variables are used among processes.
You asked why not use "normal" (statically-scoped) global variables, and I replied on the difference. That doesn't mean that I support using them like that in such languages.
Imagine environment variables didn't exist. A `su` command that modified the home directory in /etc/passwd for the duration of its subprocess to change it back when it dies would also seem pretty ugly for me. Indeed, if a language lacks dynamic scoping or environment variables didn't exist, the proper practice would be to pass the whole environment explicitly as arguments. That's what's typically done in statically-scoped languages, but it has the caveats I mentioned in this other comment:
To add to what I said yesterday, focusing on this question:
> Is there any way in which doing so via "dynamic scoping" makes it any less of a horrible idea?
In both types of languages it's a horrible thing to set a global variable, because the modification is visible to all code that runs later. If you want to know what value a global variable would have in a function, you'd need to be aware of all code that executed before.
In lexically-scoped languages, you can only read or set a variable. If you forbid setting it, you can only read it. In dynamically-scoped languages you can not only read or set a variable, you can also shadow it. Shadowing is not as bad as setting, because the "modification" is scoped to a call. Because of that, it doesn't undergo changes across functions that don't have a caller/callee relationship (e.g. cousin functions in a call-graph), and I believe that's where the real devil in modifying global variables lies, in reading a variable that could have been modified by functions that don't have a clear relationship with the current one.
Having said that, shadowing has its place. It certainly can be misused like any other feature, but I'm just answering why it's less of a horrible idea, as you put it. Though, it certainly has its benefit with no equal. Not even Haskell's Reader typeclass, because of its static-typed nature, offers exactly the same ability to modify the behavior of any callee-descendant function, unforeseen by the author of the function you're calling.
It occurs to me that this is also similar to the ability in OOP to inherit from a class or module and overriding one of its methods, only that the change, instead of being scoped to the callee-descendants, is scoped to the other methods (and their callers) that use the overriden method.
It is a bit sad that Lisp isn't taught universally anymore. It might not be the most popular language, but it offers a lot of concepts which are worthwhile studying.
Dynamically-scoped variables (more precisely: variables with global scope and dynamic extent) are one such wonderful thing. In languages which offer those, one might use one in a function to hold a state which isn't related to the primary task of that function, e.g. a stream reference to send debug information to. Sure, one could give that to the function as one of the arguments, but doing so will make the interface eventually unwieldy and carrying such state from function to function becomes a hassle. Alternatively, one could keep such state as class variable, but if it isn't directly related to the or a single class, that wouldn't be easily comprehend-able design either. Keeping such state in a plain global variable (with indefinite extent) restricts it to being a single value for all consumers.
There's always more than one way to solve a technical problem, but dynamically scoped variables are sometimes the easiest, most straight-forward and most flexible way to do so.
Technically it's possible but AFAIK people don't normally extend e.g. every business object with a logger field or an output stream? In fact that's usually a very bad pattern, something akin to a "god object".
Part of this is Lisp-only history, outside of old Lisp users dynamic scoping really has few friends although the somewhat similar environment variable mechanism is widespread and accepted:
https://en.wikipedia.org/wiki/Scope_(computer_science)#Histo...
Yeah, “12 factor apps” basically use dynamic scope for dependency injection. But, also, I believe the Reader typeclass in Haskell is basically equivalent to dynamic scope? Haskellers seem to use this and a lot of similar techniques (Free monads/mtl style) to work around the lack of dynamic scope :).
Reader is similar, but I wouldn't say equivalent, because only those things working in the Reader can use that environment. For example, when you have project A using project B using project C, you can't define a putStrLn such that project C can use it and be able to override its output stream from project A, while keeping B completely ignorant of that ability of putStrLn or that C even uses putStrLn.
Reader is like a middle-ground between dynamic-scoping and explicit passing of a context argument around as you would in a statically-scoped language.
Think about passing some ambient context around. In Go, you do it by sticking an additional "ctx context.Context" parameter in every single function that conceivably may use it directly or indirectly, i.e. may call some other function that may or may not use it.
And sometimes you need to set up a derived context for one function call, but continue to use the previous one in other calls:
It's not hard, but just extremely tedious, esp. if context is mostly utilized in the leaf functions, and the most of the rest of your code simply passes it around.