Dynamic scoping is fun and weird! That's about it for the pros. Well, also it's relatively easy to implement in an interpreter, because you don't have to do lexical resolution of names; names are resolved by walking the call stack instead of the source tree, and you already have a call stack. In practice this means your stack frames just each have a dictionary of names instead of a more complicated parent-pointer-tree of dictionaries of names. My first language, fffff, had dynamic scopes partly for this reason.
The cons are that dynamic scoping is weird and nobody is used to writing programs with it. There are almost no mainstream programming languages which use dynamic scoping; even Wikipedia only lists Logo, Emacs Lisp, LaTeX and some shell languages, and none of these are general-purpose programming languages.
Dynamic scoping makes it pretty difficult to reason about programs, particularly if you have higher-order functions. Consider the following example:
def call_me_maybe(f):
p = random()
if p < 0.5:
f()
x = 23
call_me_maybe(() => print(x));
This program has a 50% probability of printing the number 23. But if we refactor the call_me_maybe
function to change the name of the local variable p
to x
, then suddenly the program instead has a 50% probability of printing a floating-point number between 0 and 0.5. This is because the local variable in the call_me_maybe
stack frame shadows the one from the root stack frame.
So the behaviour of a piece of code like this can't be determined without knowing even the names of the local variables of all the functions that might be called! This is a pretty severe violation of encapsulation, the principle that one piece of code shouldn't depend on the irrelevant internal details of another piece of code.
So this is pretty much why sensible languages don't use dynamic scoping. That said, there is some benefit in having something resembling dynamic scope for individual variables on an opt-in basis, in a language which is otherwise lexically scoped. Scala, for example, has this for function parameters, as does my language Papyri:
@fn greet($greeting: implicit str) $name: str -> {
$greeting, $name!
}
@fn say_hi $name: str -> {
@implicit(greeting=`Hi`)
@greet $name
}
@say_hi `Andrew`
# result: Hi, Andrew!
An implicit parameter will be passed automatically when the function is called, with the argument value taken from a (lexically-scoped) variable of the same name, as long as that variable is also labelled as implicit. So this is still lexical scoping because the name $greeting
in the first function refers to a different variable than the same name in the second function, but it behaves similar to dynamic scoping because the value is passed automatically along the call stack.
this
in javascript is dynamically scoped. $\endgroup$upvar
. $\endgroup$