Как определить функцию, которая принимает в качестве аргумента литерал функции (с неявным параметром)? - PullRequest
3 голосов
/ 15 августа 2011

Я хочу иметь возможность что-то делать в этих строках (не компилируется):

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

А затем используйте его так:

logScope(new ConsoleLogger){logger =>
    operationOne
    operationTwo
    }

Но самое близкое, что я нашел к рабочему решению, это:

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
    }

Я не думаю, что в настоящее время язык допускает такие конструкции, но все же, какие-либо предложения или обходные пути для достижения аналогичных результатов?


незначительное обновление: я создал гист с немного расширенной версией приведенного выше кода с парой попыток имитировать этот тип литерала. На данный момент версия CheatEx является лучшей.

Ответы [ 2 ]

6 голосов
/ 16 августа 2011

Во втором примере попробуйте это:

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

Это должно работать нормально. Логика здесь в том, что «неявный» является атрибутом определенного значения внутри замыкания, а не частью интерфейса замыкания.

2 голосов
/ 06 ноября 2012

Другим решением является использование динамического шаблона области действия *1001* вместо неявных параметров. Вы можете даже объединить оба, как это:

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" ) }

Пример использования:

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

Что приводит к:

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

Текущий регистратор неявно передается «за пределы» (мы просто используем глобальную (и локальную переменную) переменную для хранения текущего регистратора). Мы вполне могли бы никогда не упомянуть Logger где-либо в сигнатурах методов и напрямую вызвать currentLoggerVar.value. Отмена доступа к currentLoggerVar.value внутри неявного значения Logger по умолчанию (прокси DynamicScopeLogger) позволяет нам не трогать методы ведения журнала. Это также означает, что мы можем использовать динамическую область действия по умолчанию и при необходимости переопределять это поведение, просто определяя неявный локальный Logger, который затем будет иметь приоритет над DynamicScopeLogger.

Основные недостатки:

  • В зависимости от требований к скорости может быть слишком медленным: доступ к локальному хранилищу потока имеет свою стоимость, включая (но не ограничиваясь этим) поиск в карте локальных переменных потока.

  • Это зависит от того факта, что лексическая область видимости соответствует порядку выполнения (что обычно имеет место, но не всегда). Как только это больше не так, вы столкнетесь с неприятностями. Например, при вызове map или flatMap для scala.concurrent.Future (или просто Future.apply) тело map / flatMap может быть выполнено в другом потоке, и, следовательно, тело не обязательно будет использовать ожидаемый регистратор:

    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    
    

    В приведенном выше примере operationOne называется с лексической областью logScope, поэтому мы можем ожидать получить сообщение "Customized Logger 1: Inside operationOne", однако мы видим, что вместо этого используется регистратор по умолчанию. Это связано с тем, что выполнение тела Future.apply откладывается и происходит позже в другом потоке (после того, как мы сбросили переменную Logger.currentLoggerVar к ее значению по умолчанию).

...