Skip to content

Comment on Dynamic Scoping in C++parent

Comments

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

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.

AboutSource Built by g1lg1l

Hackerly is an independent reader for Hacker News, built on the public HN API. Not affiliated with Y Combinator.