I want to be able to do something on these lines (won't compile):
def logScope(logger:Logger)(operation: (implicit l:Logger) => Unit) {/* code */ operation(logger) /* code */}
def operationOne(implicit logger:Logger) {/**/}
def operationTwo(implicit logger:Logger) {/**/}
And then use it like so:
logScope(new ConsoleLogger){logger =>
operationOne
operationTwo
}
But the nearest I've come to a working solution is this:
def logScope(logger:Logger)(operation: Logger => Unit) {/* code */ operation(logger) /* code */}
def operationOne(implicit logger:Logger) {/**/}
def operationTwo(implicit logger:Logger) {/**/}
/* other code */
logScope(new ConsoleLogger){logger =>
implicit val l = logger
operationOne
operationTwo
}
I don't think the language currently allows such constructs, but still, any suggestions or workarounds to achieve similar results?
minor update: I've created a gist with a slightly expanded version of the above code with a couple of attempts at simulating this kind of literal. As of now, CheatEx's version is the best one.
Another solution is to rely on the dynamic scope pattern instead of implicit parameters. You can actually even combine both, like this:
A usage example:
Which results in:
The current logger is passed implicitly "out of bounds" (we just use a global (and thread local) variable to store the current logger). We could every well never mention
Logger
anywhere in the method signatures, and directly callcurrentLoggerVar.value
. Lifting the access tocurrentLoggerVar.value
inside a default implicit Logger value (theDynamicScopeLogger
proxy) allows us to keep the logging methods untouched. It also means that we can use dynamic scope by default, and override this behaviour when needed by simply defining a local Logger implicit that will then take precedence overDynamicScopeLogger
.The main disadvantages are:
Depending on the speed requirements, may be too slow: accessing thread local storage has a cost, including (but not limited to) a lookup in map of the thread local variables.
This relies on the fact that the lexical scoping matches the order of execution (which is generally the case, but not always). As soon as it's not the case anymore, you will run into troubles. By example when calling map or flatMap on a
scala.concurrent.Future
(or simplyFuture.apply
), the body of the map/flatMap may be executed in another thread, and thus the body will not necessarily use the expected logger:In the above example,
operationOne
is called witihn the lexical scope oflogScope
, so we might expect to get the message"Customized Logger 1: Inside operationOne"
, however we see that the default logger is used instead. This is because the execution of theFuture.apply
's body is deferred and happens later on, on another thread (after we have reset the variableLogger.currentLoggerVar
to its default value).In your second example try this:
It should work fine. The logic here is that 'implicit' is an attribute of particular value inside closure, not a part of the closure's interface.