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);
}
}
You've already created at least one inconsistency, and a particularly damning one at that - treating functions differently from other values. A function declaration is conceptually the same as binding a variable to an anonymous function, but you're asserting that "regular" variables don't get hoisted while function declarations do. So...
f()
function f() { /* ... */ }
Works, but...
f()
var f = function() { /* ... */ } // Pretend this is anonymous function syntax
Doesn't. (And if you don't allow passing functions around like any other value, your language is horribly crippled.) Next, what happens if a function is redefined?
f()
function f() { /* definition 1 */ }
...
function f() { /* definition 2 */ }
If you treat this as an error, you're once again rejecting functions as values. There's no reason you shouldn't be able to do:
var f = function() { /* def 1 */ }
f = function() { /* def 2 */ }
So let's assume you'll allow the function to be redefined. How do you deal with lexical scoping?
function f() { /* definition 1 */ }
function bar() {
f()
function f() { /* definition 2 */ }
If you hoist definition 2 to the top of bar
's scope, you end up with this:
function f() { /* definition 1 */ }
function bar() {
function f() { /* definition 2 */ }
f()
Which means you can never call the outer definition from a nested scope unless you give the inner function a different name. Moreover, in any other language, the meaning of the above program is independent of the choice of name for definition 2. The hoisting rule changes the meaning depending on whether there's a name collision or not. On the other hand if you don't hoist definition 2 to the top of bar
, you've broken your own rule.
Best Answer
In theory, function scope should actually be faster - variables are typically created in a stack frame, and function scope would only create that stack frame once, while block scope would need to repeadedly open up a new stack frame in every block.
In practice, there are several facts that invalidate that initial assumption:
So, from a performance viewpoint, I don't actually see advantages for both approaches - As pointed out above, you can technically implement function scope even if the language asks for block scope. That might waste some memory, though.
From a programmer's standpoint, I do see, however, clear advantages for block scope (that might, however, just be because I grew up with languages like C and C++)