2010-11-24 20 views
8

Supongamos que deseo ajustar el código que puede generar excepciones con un bloque try-catch que registra la excepción y continúa. Algo así como:¿Es posible pasar "esto" como parámetro implícito en Scala?

loggingExceptions { 
    // something dangerous 
} 

Idealmente, me gustaría utilizar para registrar el registrador definido en el objeto que llama, en su caso (y en su defecto, obtener un error en tiempo de compilación). Me gustaría definir algo como esto:

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

donde objectWithLogger de alguna manera "mágica" para ampliar "este" código en el cliente. ¿Es esto (o algo similar) posible?

Respuesta

11

De hecho, puede hacerse del mismo modo que desee. Los otros respondedores se rindieron demasiado rápido. ¡No hay banderas blancas!

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 
+2

+1 para el ejemplo. Esto se puede hacer, pero es desaconsejable. No separa la preocupación de registro, así como un rasgo de Registro, requiere "magia implícita" e incurre en sobrecarga de tiempo de compilación (búsqueda implícita) y tiempo de ejecución (reflexión) innecesarios. La implementación oculta el hecho de que la conversión implícita debe estar dentro del alcance, y en una base de código más realista, esto requeriría una declaración de importación en el código del cliente. –

+4

Su lista de negativos resume a "Prefiero resolver un problema diferente". Bueno, sí, ¿no es así? – extempore

+0

No realmente. El problema fundamental es registrar excepciones en un bloque de código dentro de una clase que tiene un registrador. Puedes resolver ese problema de la forma en que el PO originalmente se imaginó (con implicits), o puedes resolverlo de forma mucho más simple con un rasgo. –

3

Puede agregar un rasgo a todas las clases que quieran usar def loggingExceptions y en este rasgo agregar un tipo propio que espera que def logger: Logger esté disponible.

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

Gracias, esto funciona! ¿Pero hay otras soluciones que no impliquen cambiar la declaración de todas las clases que desean usar loggingExceptions (...)? –

+0

@JPP No, al menos el sitio de llamada necesitará tener un objeto implícito del tipo esperado en el alcance. Por ejemplo, puede hacer que el parámetro implícito sea un 'Logger', y cambiar el' def logger' por el 'implogger' implícito' en el objeto que llama. Sin embargo, las implicaciones deben evitarse a menos que sea necesario, y un rasgo es adecuado para este problema. –

4

Debilski's answer va a funcionar, pero no estoy seguro de que veo una buena razón para utilizar un tipo estructural (es decir, { def logger: Logger }) aquí. De lo contrario, se incurrirá en una sobrecarga de tiempo de ejecución adicional cuando se invoca logger, ya que la implementación de tipos estructurales depende de la reflexión. El método loggingExceptions está estrechamente ligada a la tala, por lo que acaba de hacer que sea parte de un rasgo de registro:

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 { 
     // ... 
     } 
    } 
} 
Cuestiones relacionadas