Dynamic scoping seems strangely awkward to implement despite having in some ways nicer semantics.
Dynamic scope is arguably easier to implement than lexical scope ─ it means instead of building a tree structure of lexical scopes and searching that, you resolve names at runtime by searching the call stack for a stack frame where the name exists.
The issue is really that no sensible language wants to have only dynamic scope, because lexical scope is much better suited for problem-solving, and people are used to it. (See my related answer here.) So if your choice is between either just lexical scope, or both lexical scope and dynamic scope, then the latter is indeed more work to implement.
Assuming you don't want to use only dynamic scope, then probably you want names to be lexically scoped by default, and so you need some way to allow certain variables to "opt in" to dynamic scoping. A good option is to automatically pass dynamically scoped variables down the call stack when a function which uses one of these variables is called, instead of searching up the call stack when one of these variables is used. This way, there is no change to how names are resolved, instead the only change is in how parameters are passed.
The result is how "implicit" variables in Scala and in my own language Papyri work. Here's an example of a hypothetical syntax which demonstrates this:
def foo(implicit x):
print("Hello,", x)
def bar():
implicit x = "Andrew"
foo()
At the call to foo()
, we check whether it has any implicit
parameters ─ in this case, x
is one. The argument value for x
is found by resolving the same name in the caller's scope, and then passed to the function in the same way any other argument would be passed. This could be done either in a compiled or an interpreted language:
- In a statically typed compiled language, the
implicit
parameters would be known at compile-time, so the call could be desugared into one which passes the arguments explicitly, and there would be no need for anything different to happen at runtime.
- Otherwise, the
implicit
parameters would be known from foo
's signature at runtime, and the resolution of those names would happen at the call-site before foo
is invoked.
Note also that in this hypothetical syntax, the variable x
in the caller's scope is labelled as implicit
when it is declared, i.e. bar
has to "consent" to this variable being passed to the function foo
. This is a good idea because it means a change to the signature of foo
can't result in foo
having access to one of bar
's local variables which bar
didn't intend to expose. However, this is just a safety measure and it's not strictly needed for the feature to work.