Hacker News new | ask | show | jobs
by Verdex_3 2869 days ago
With respect to the call stack, a dynamically scoped variable is like the dual of an exception. That is to say, an exception is a way for a method to communicate with methods that are above it in the call stack. And a dynamically scoped variable is a way for a method to communicate with methods below it in the call stack.

Of the two the dynamically scoped variable is more powerful than the exception because an exception can only propagate upwards. A dynamically scoped variable can propagate downward, upward, or sideways (ie sibling methods in the call graph can access the values that you placed into the dynamically scoped variable).

In terms of how much trouble you can get into by misusing it, the dynamically scoped variable is more problematic than the exception (all things being equal). A dynamically scoped variable is basically just a global variable that isn't quite 100% global. So you can get into all the same problems that a global can get you into with the added benefit (or sometimes problem) that any two instances of a dynamically scoped variable might actually be different because they are coming from a different caller.

Finally, there are some really good usages of a dynamically scoped variable. Consider reference counting. If you're doing reference counting every time you increase/decrease your reference count, you are going to need to modify a counter. This is fine if you only have one thread, but with multiple threads it means that every time you modifying the counter you'll need to lock. Not something you want to do if you're worried about speed. However, if you stuck your reference counting framework into dynamic variables that get populated in the thread spin up method, then you could guarantee that each reference counting instance is inside of it's own thread. Now you don't have to lock anymore when you modify the ref count (as long as you don't otherwise cross the thread barrier).

All in all, I suspect that it's better that we have lexical scoping as a standard. Most of the things that I want to do with dynamic scoping, I feel like should live in a language feature or library. Additionally, dynamic scoping is more likely to break other abstractions like object oriented programming (ie we can't think of the object as an encapsulated unit if the way that it functions is determined by the methods above it in the call stack).

1 comments

These examples don't really do dynamic scoping justice, and there are a few inaccuracies here.

> A dynamically scoped variable can propagate downward, upward, or sideways (ie sibling methods in the call graph can access the values that you placed into the dynamically scoped variable).

The binding only propagates downwards, never sideways or upwards.

You can send values through bindings upwards, downwards, or sideways, but all you're doing is assigning a value to a memory location. This isn't particularly novel, because you can do the same thing with any other type of variable, or with fields in a structure, etc.

> A dynamically scoped variable is basically just a global variable that isn't quite 100% global. So you can get into all the same problems that a global can get you into with the added benefit (or sometimes problem) that any two instances of a dynamically scoped variable might actually be different because they are coming from a different caller.

This is no more a problem than it's a problem that when you call f(x), you might get a different value for x each time you call it, because x comes from the caller.

The common use cases for dynamic variables are certain seldom-changed parameters for functions and maintaining context. The basic reason for using them is to avoid having to pass a potentially large set of variables through a potentially large number of intermediate functions. Dynamic variables are most similar to thread-local variables.

For seldom-changed parameters, consider standard-output (asterisks and HN don't mix). It's common to want to redirect the output of some block of code to a different location, in a shell script you could do this very simply:

    {
      echo a
      echo b
    } >file.txt
In Lisp, you can do this by binding the standard-output variable:

    (with-open-file (stream "file.txt" :direction :output)
      (let ((*standard-output* stream))
        (print 'a)
        (print 'b)))
You can see that this isn't really some mysterious or dangerous phenomenon, it's a way to pass things down to functions without having to pass them as arguments. It's also commonly used for tracking the context of things like requests in a web server. In Haskell you'd do this by using a reader monad, and in Go you'd do this by adding entries a dictionary attached to your context.Context object.
This should be sufficient:

  (with-open-file (*standard-output* "file.txt" :direction :output)
    (print 'a)
    (print 'b))