2010-11-24 2 views
8

Предположим, я хочу обернуть код, который может генерировать исключения с помощью блока try-catch, который регистрирует исключение и продолжает. Что-то вроде:Можно ли передать «это» как неявный параметр в Scala?

loggingExceptions { 
    // something dangerous 
} 

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

def loggingExceptions[L <: { def logger: Logger }](work: => Unit)(implicit objectWithLogger: L): Unit = { 
    try { 
    work 
    } catch { 
    case t: Exception => objectWithLogger.logger.error(t.getMessage) 
    } 
} 

где objectWithLogger бы как-то «волшебным образом» расширить до «этого» в код клиента. Возможно ли это (или подобное)?

ответ

11

Это на самом деле может быть сделано так же, как вы хотите. Другие ответчики сдались слишком быстро. Нет белых флагов!

package object foo { 
    type HasLogger = { def logger: Logger } 
    implicit def mkLog(x: HasLogger) = new { 
    def loggingExceptions(body: => Unit): Unit = 
     try body 
     catch { case ex: Exception => println(ex) } 
    } 
} 

package foo { 
    case class Logger(name: String) { } 

    // Doesn't compile: 
    // class A { 
    // def f = this.loggingExceptions(println("hi")) 
    // } 
    // 1124.scala:14: error: value loggingExceptions is not a member of foo.A 
    //   def f = this.loggingExceptions(println("hi")) 
    //     ^
    // one error found 

    // Does compile 
    class B { 
    def logger = Logger("B") 
    def f = this.loggingExceptions(println("hi")) 
    def g = this.loggingExceptions(throw new Exception) 
    } 
} 

object Test { 
    def main(args: Array[String]): Unit = { 
    val b = new foo.B 
    b.f 
    b.g 
    } 
} 

// output 
// 
// % scala Test 
// hi 
// java.lang.Exception 
3

Вы можете добавить признаку ко всем классам, которые хотят использовать def loggingExceptions, и в этом признаке добавить свой тип, который ожидает def logger: Logger.

trait LoggingExceptions { 
    this: { def logger: Logger } => 
    def loggingExceptions(work: => Unit) { 
    try { work } 
    catch { case t: Exception => logger.error(t.getMessage) } 
    } 
} 

object MyObjectWithLogging extends OtherClass with LoggingExceptions { 
    def logger: Logger = // ... 

    def main { 
    // ... 
    loggingExceptions { // ... 
    } 
    } 
} 
+0

Спасибо, это работает! Но есть ли другие решения, которые не связаны с изменением объявления всех классов, которые хотят использовать loggingExceptions (...)? – 2010-11-24 19:23:55

+0

@JPP Нет, по крайней мере, у callsite должен быть неявный объект ожидаемого типа в области видимости. Например, вы можете сделать неявный параметр `Logger` и изменить` def logger` на `implicit def logger` в вызывающем объекте. Тем не менее, импликации следует избегать, если это необходимо, и черта подходит для этой проблемы. – 2010-11-24 19:47:25

4

Debilski's answer будет работать, но я не уверен, что я вижу хороший повод использовать структурный тип (т.е. { def logger: Logger }) здесь. Это приведет к дополнительным накладным расходам во время работы при вызове logger, поскольку реализация структурных типов зависит от отражения. Метод loggingExceptions тесно связана с лесозаготовок, так что я бы просто сделать его частью Logging признака:

trait Logging { 
    def logger: Logger 

    final def loggingExceptions(body: => Unit) = 
     try body catch { case e: Exception => logger.error(e.getMessage) } 
} 

trait ConcreteLogging extends Logging { 
    val logger = // ... 
} 

object MyObject extends SomeClass with ConcreteLogging { 
    def main { 
     // ... 
     loggingExceptions { 
     // ... 
     } 
    } 
} 
Смежные вопросы