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.
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?
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:
https://news.ycombinator.com/item?id=24545180
> I'm having trouble making sense of your first paragraph.
Here's an example using Elisp:
; Turning off static-scoping
(setq lexical-binding nil)
;; Bad practice
(defun foo ()
bar)
(foo)
;=> Debugger entered--Lisp error: (void-variable bar)
(let ((bar 3))
(foo))
;=> 3
;; Good practice
(defvar bar 2)
(foo)
;=> 2
(let ((bar 3))
(foo))
;=> 3Dynamically-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.
And sometimes you need to set up a derived context for one function call, but continue to use the previous one in other calls:
func Foo(ctx context.Context, ...) {
Bar1(ctx, ...)
derived := makeDerivedContext(ctx)
Bar2(derived, ...)
Bar3(ctx, ...)
// ...
}
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.That's where dynamic scoping enters in.