How to define a function that takes a function literal (with an implicit parameter) as an argument?

前端 未结 2 1273
情书的邮戳
情书的邮戳 2021-01-14 05:45

I want to be able to do something on these lines (won\'t compile):

def logScope(logger:Logger)(operation: (implicit l:Logger) => Unit) {/* code */ operati         


        
相关标签:
2条回答
  • 2021-01-14 06:04

    In your second example try this:

    logScope(Logger()) { implicit logger =>
      operationOne
    }
    

    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.

    0 讨论(0)
  • 2021-01-14 06:15

    Another solution is to rely on the dynamic scope pattern instead of implicit parameters. You can actually even combine both, like this:

    import scala.util.DynamicVariable
    object Logger {
      val defaultLogger = new ConsoleLogger( "DEFAULT: %s" )
      val currentLoggerVar = new DynamicVariable[Logger]( defaultLogger )
      implicit object DynamicScopeLogger extends Logger {
        def log( msg: Any* ) {
          currentLoggerVar.value.log( msg: _* )
        }
      }
    }
    trait Logger {
      def log( msg: Any* )
    }
    class ConsoleLogger( val pattern: String ) extends Logger {
      def log( msg: Any* ) { println( pattern.format( msg: _* ) ) }
    }
    
    def logScope[T](logger: Logger)( operation: => T ): T = {
      Logger.currentLoggerVar.withValue( logger )( operation )
    }
    def operationOne(implicit logger: Logger) { logger.log( "Inside operationOne" ) }
    def operationTwo(implicit logger: Logger) { logger.log( "Inside operationTwo" ) }
    def operationThree(implicit logger: Logger) { logger.log( "Inside operationThree" ) }
    def operationFour(implicit logger: Logger) { logger.log( "Inside operationFour" ) }
    

    A usage example:

    operationOne
    logScope(new ConsoleLogger("Customized Logger 1: %s")){
      operationTwo
      logScope(new ConsoleLogger("Customized Logger 2: %s")){
        operationThree
      }
      operationFour
    }
    

    Which results in:

    DEFAULT: Inside operationOne
    Customized Logger 1: Inside operationTwo
    Customized Logger 2: Inside operationThree
    Customized Logger 1: Inside operationFour
    

    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 call currentLoggerVar.value. Lifting the access to currentLoggerVar.value inside a default implicit Logger value (the DynamicScopeLogger 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 over DynamicScopeLogger.

    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 simply Future.apply), the body of the map/flatMap may be executed in another thread, and thus the body will not necessarily use the expected logger:

      scala>import scala.concurrent.Future
      import scala.concurrent.Future
      scala>import scala.concurrent.ExecutionContext.Implicits.global
      import scala.concurrent.ExecutionContext.Implicits.global
      scala>logScope(new ConsoleLogger("Customized Logger: %s")){
           |  Future{ operationOne }
           |}
      DEFAULT: Inside operationOne
      res5: scala.concurrent.Future[Unit] = scala.concurrent.impl.Promise$DefaultPromise@1a38913    
      

      In the above example, operationOne is called witihn the lexical scope of logScope, 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 the Future.apply's body is deferred and happens later on, on another thread (after we have reset the variable Logger.currentLoggerVar to its default value).

    0 讨论(0)
提交回复
热议问题