A very useful application of dynamic scoping is for passing contextual parameters without having to add new parameters explicitly to every function in a call stack
For example, Clojure supports dynamic scoping via binding, which can be used to temporarily reassign the value of *out*
for printing. If you re-bind *out*
then every call to print within the dynamic scope of the binding will print to your new output stream. Very useful if, for example, you want to redirect all printed output to some kind of debugging log.
Example: in the code below, the do-stuff function will print to the debug output rather than standard out, but note that I didn't need to add an output parameter to do-stuff to enable this....
(defn do-stuff []
(do-other-stuff)
(print "stuff done!"))
(binding [*out* my-debug-output-writer]
(do-stuff))
Note that Clojure's bindings are also thread-local, so you don't have an issue with concurrent usage of this capability. This makes bindings considerably safer than (ab)using global variables for the same purpose.
Limit the use of scopes or more concretely Use scopes for wiring only.
Used properly, scopes can reduce a lot of factory boiler plate. I use scopes to wire together sub-processes that may need access to its name and arguments. This is similar to the RequestScope
s provided by Guice and Spring.
But scopes are effectively a thread-local map of string to object. This is practically a Global Variable Depot. This is why I limit my scope usage.
This leads to the corollary:
Hide the scopes or more generally Hide your DI Framework
Because scopes (and DI frameworks) are essentially Global Variable Depots, I prefer to encapsulate the DIF such that the only thing that knows there is a DIF is the main method.
To do this, I define my own scope interface and my own ProcessFactory that I define within a Guice module.
Because of this, my process manager is free of references to Guice. In fact, it is little more than this method:
void run(final ProcessContext context) {
try {
this.scope.enter(context.processArgs());
this.factory.create(args.processName());
} finally {
this.scope.exit();
}
}
Here's the complete Guice module that binds and hides my use of a Guice. It implements my own ProcessScope and ProcessFactory interfaces. It binds @ProcessParameters so they can be injected and a few convienience objects as well (@ProcessName String and ProcessConfig).
public class ProcessRunnerModule extends AbstractModule {
private static final String PROCESS_RUN_SCOPE = "ProcessRunScope";
/**
* Objects of type Map<String, Object> parameterized by @ProcessParameters
* are injected in the ProcessRunScope
*/
static final Key<Map<String, Object>> PROCESS_PARAMETERS_KEY;
static {
final TypeLiteral<Map<String, Object>> MAP_TYPE = new TypeLiteral<Map<String, Object>>() {
};
PROCESS_PARAMETERS_KEY = Key.get(MAP_TYPE, ProcessParameters.class);
}
/**
* Wraps Guice scope to ProcessScope. Injects the @ProcessParameters into guice
*/
private static class GuiceScopeAdapter implements ProcessScope {
private final SimpleScope scope;
@Inject @SuppressWarnings("unused")
public GuiceScopeAdapter(@Named(PROCESS_RUN_SCOPE) SimpleScope scope) {
this.scope = scope;
}
@Override
public void enterScope(Map<String, Object> parameters) {
scope.enter();
scope.seed(PROCESS_PARAMETERS_KEY, parameters);
}
@Override
public void exitScope() {
scope.exit();
}
}
/**
* Processes are run and bound in @ProcessRunScope.
*/
protected void configure() {
final SimpleScope processRunScope = new SimpleScope();
bindScope(ProcessRunScope.class, processRunScope);
bind(SimpleScope.class)
.annotatedWith(Names.named(PROCESS_RUN_SCOPE))
.to(processRunScope);
}
/**
* This wraps Processes bound via MapBinder to a ProcessFactory
*/
@Provides @Singleton
ProcessFactory createProcessFactory(final Map<String, Provider<Process>> processFactories) {
log.info("Instantiating process factory", "known-processes", processFactories.keySet());
return new ProcessFactory() {
@Override
public Process create(final String name) {
return processFactories.get(name).get();
}
};
}
/**
* ProcessRunner does not know about Guice
*/
@Provides @Singleton
ProcessRunner createProcessRunner(
final ProcessScope processScope,
final ProcessFactory processFactory) {
return new ProcessRunner(processScope, processFactory);
}
/**
* Convienience: bind a @ProcessName extracted from the @ProcessParameters
*/
@Provides @ProcessName @ProcessRunScope
String bindProcessName(final @ProcessParameters Map<String, Object> params) {
return params.get(ProcessRunner.PROCESS_NAME_KEY).toString();
}
/**
* Convienience: bind a ProcessConfig wrapping the @ProcessParameters
*/
@Provides @ProcessRunScope
ProcessConfig createParamHelper(final @ProcessParameters Map<String, Object> params) {
return new ProcessConfig(params);
}
}
Best Answer
Dynamic scope seems to imply, and for good reason, that there's a model whereby scope can be determined dynamically at runtime, rather than statically at author-time. That is in fact the case.
Let's illustrate via code:
Lexical scope holds that the RHS reference to
a
infoo()
will be resolved to the global variablea
, which will result in value 2 being output.Dynamic scope, by contrast, doesn't concern itself with how and where functions and scopes are declared, but rather where they are called from. In other words, the scope chain is based on the call-stack, not the nesting of scopes in code.
So, if JavaScript had dynamic scope, when
foo()
is executed, theoretically the code below would instead result in 3 as the output.How can this be? Because when
foo()
cannot resolve the variable reference fora
, instead of stepping up the nested (lexical) scope chain, it walks up the call-stack, to find wherefoo()
was called from. Sincefoo()
was called frombar()
, it checks the variables in scope forbar()
, and finds ana
there with value 3.Strange? You're probably thinking so, at the moment.
But that's just because you've probably only ever worked on (or at least deeply considered) code which is lexically scoped. So dynamic scoping seems foreign. If you had only ever written code in a dynamically scoped language, it would seem natural, and lexical scope would be the odd-ball.
To be clear, JavaScript does not, in fact, have dynamic scope. It has lexical scope. Plain and simple. But the
this
mechanism is kind of like dynamic scope.The key contrast: lexical scope is write-time, whereas dynamic scope (and this!) are runtime. Lexical scope cares where a function was declared, but dynamic scope cares where a function was called from.
Finally:
this
cares how a function was called, which shows how closely related thethis
mechanism is to the idea of dynamic scoping.Source: You Don't Know JS: Scope & Closures