Here are some thoughts, as I don’t think that this is a simple yes-or-no question.
I don’t think that it makes sense to call dynamic variables »yucky«. They can be useful in some constructs where you actually use the context-establishing properties of the call stack. You could also pass that context explicitly, but when you have higher level functions in between, that can sometimes become unwieldy. As an example, look at printer control variables in Common Lisp.
The down side is that such context gets lost as soon as you go to a different thread, such as with a
go block. That is, stack-based programming (such as with exceptions and dynamic variables) is a bit at odds with multi-threaded code.
Common Lisp was designed at a time where multi-threadedness was still in its infancy, and the language standard doesn’t even have anything about that (the de facto threading standard,
bordeaux-threads, is just a wrapper library over the different CL implementations). If you know that your program (or even just your request handler) operates on a single stack, then you can very well make use of that, as it simplifies things. Common Lisp gives you quite some handy tools for that, such as the three-tier condition system, stack allocation, and dynamic variables (which strictly speaking are even independent from global variables, i. e. you can have local variables with dynamic extent).
(Aside: no, the CL condition system is not „implemented with dynamic variables“, even if some implementations of CL might under the hood do exactly that. The CL standard doesn’t define conditions in terms of dynamic variables.)
In Clojure, as far as I am aware, dynamic extent is limited to
Vars, which might have an impact on usefulness. Also, multi-threaded code is much more common, such as handing something off to a
go block. In such an environment, stack-based features are more limited in use, and I e. g. tend to avoid throwing exceptions, returning anomalies instead (no, exceptions are not »yucky« either).
But to come back to my point: I think dynamic vars are useful if you want to establish a context or environment that fine-tunes the behaviour of low-level functions, and you have a good grasp on the involved stacks/threads. I don’t want to see people now running off and using them everywhere. But if the situation fits, it’s better to use them than shoehorning it into some awkward construct that philosophically feels more »functional« but actually just re-implements dynamic vars worse.
By the way, namespaces are global variables. Mount just operates on this level to keep a system consistent in the face of re-loading single namespaces. If your program is simple enough, this can be totally sufficient. Same with database connections: if you have only a single connection over the live runtime of your program, using a global var is sufficient. If you want just a little control, e. g. for testing or other temporary things, you can make it dynamic, so that you can use a different connection in the context of some call:
(binding [*db* (hey-lets-use-the backup)]
Making global variables dynamic makes them much more useful. In simple cases, you can get away with that, instead of defining a Component, a System, a Startup Handler etc. Adding layers of indirection does also have downsides.
Namespaces are global state, as are the vars most of our functions are stored in. Global state is good for things that we don’t want to change. Dynamic state is good for things that we want to change only in the context of a call.
(Sorry for the long, rambling posting, I lacked the time to write a short one.)