| > 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: 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))
;=> 3
|
> 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.