cómo mantener el valor de retorno al iniciar sesión en scala

Cuando programo en Java, siempre registro el parámetro de entrada y el valor de retorno de un método, pero en scala, la última línea de un método es el valor de retorno. entonces tengo que hacer algo como:

def myFunc() = { val rs = calcSomeResult() logger.info("result is:" + rs) rs } 

para hacerlo más fácil, escribo una utilidad:

 class LogUtil(val f: (String) => Unit) { def logWithValue[T](msg: String, value: T): T = { f(msg); value } } object LogUtil { def withValue[T](f: String => Unit): ((String, T) => T) = new LogUtil(f).logWithValue _ } 

Entonces lo usé como:

 val rs = calcSomeResult() withValue(logger.info)("result is:" + rs, rs) 

registrará el valor y lo devolverá. funciona para mí, pero parece extraño. como soy un progtwigdor de Java antiguo, pero nuevo en scala, no sé si hay una forma más idiomática de hacer esto en Scala.


gracias por su ayuda, ahora creo una mejor utilidad usando el combinador Kestrel mencionado por romusz

 object LogUtil { def kestrel[A](x: A)(f: A => Unit): A = { f(x); x } def logV[A](f: String => Unit)(s: String, x: A) = kestrel(x) { y => f(s + ": " + y)} } 

Agrego el parámetro f para que pueda pasarle un registrador desde slf4j, y el caso de prueba es:

 class LogUtilSpec extends FlatSpec with ShouldMatchers { val logger = LoggerFactory.getLogger(this.getClass()) import LogUtil._ "LogUtil" should "print log info and keep the value, and the calc for value should only be called once" in { def calcValue = { println("calcValue"); 100 } // to confirm it's called only once val v = logV(logger.info)("result is", calcValue) v should be === 100 } } 

Lo que estás buscando se llama Kestrel combinator (K combinator): Kxy = x . Puede realizar todo tipo de operaciones de efectos secundarios (no solo el registro) mientras devuelve el valor que se le transfiere. Lea https://github.com/raganwald/homoiconic/blob/master/2008-10-29/kestrel.markdown#readme

En Scala, la forma más sencilla de implementarlo es:

  def kestrel[A](x: A)(f: A => Unit): A = { f(x); x } 

Luego puede definir su función de impresión / registro como:

 def logging[A](x: A) = kestrel(x)(println) def logging[A](s: String, x: A) = kestrel(x){ y => println(s + ": " + y) } 

Y úsalo como:

 logging(1 + 2) + logging(3 + 4) 

su función de ejemplo se convierte en un trazador de líneas:

 def myFunc() = logging("result is", calcSomeResult()) 

Si prefiere la notación OO, puede usar implícitos como se muestra en otras respuestas, pero el problema con dicho enfoque es que creará un objeto nuevo cada vez que quiera registrar algo, lo que puede causar degradación del rendimiento si lo hace con la frecuencia suficiente. Pero para completar, se ve así:

 implicit def anyToLogging[A](a: A) = new { def log = logging(a) def log(msg: String) = logging(msg, a) } 

Úselo como:

 def myFunc() = calcSomeResult().log("result is") 

Si le gusta un enfoque más genérico, puede definir

 implicit def idToSideEffect[A](a: A) = new { def withSideEffect(fun: A => Unit): A = { fun(a); a } def |!>(fun: A => Unit): A = withSideEffect(fun) // forward pipe-like def tap(fun: A => Unit): A = withSideEffect(fun) // public demand & ruby standard } 

y usarlo como

 calcSomeResult() |!> { rs => logger.info("result is:" + rs) } calcSomeResult() tap println 

Tienes la idea básica correcta, solo tienes que arreglarla un poco para que sea lo más conveniente posible.

 class GenericLogger[A](a: A) { def log(logger: String => Unit)(str: A => String): A = { logger(str(a)); a } } implicit def anything_can_log[A](a: A) = new GenericLogger(a) 

Ahora usted puede

 scala> (47+92).log(println)("The answer is " + _) The answer is 139 res0: Int = 139 

De esta forma no es necesario que te repitas (por ejemplo, no hay dos veces).

Digamos que ya tienes una clase base para todos tus registradores:

 abstract class Logger { def info(msg:String):Unit } 

Entonces podrías extender String con el método @@ logging:

 object ExpressionLog { // default logger implicit val logger = new Logger { def info(s:String) {println(s)} } // adding @@ method to all String objects implicit def stringToLog (msg: String) (implicit logger: Logger) = new { def @@ [T] (exp: T) = { logger.info(msg + " = " + exp) exp } } } 

Para usar el registro, debe importar miembros del objeto ExpressionLog y luego puede registrar fácilmente expresiones usando la siguiente notación:

 import ExpressionLog._ def sum (a:Int, b:Int) = "sum result" @@ (a+b) val c = sum("a" @@ 1, "b" @@2) 

Se imprimirá:

 a = 1 b = 2 sum result = 3 

Esto funciona porque cada vez que llamas a un método @@ en un comstackdor de String da cuenta de que String no tiene el método y lo convierte silenciosamente en un objeto de tipo anónimo que tiene el método @@ definido (ver stringToLog ). Como parte del comstackdor de conversión selecciona el registrador deseado como un parámetro implícito , de esta manera no tiene que seguir pasando el registrador a @@ cada vez que tenga control total sobre qué registrador necesita usarse cada vez.

En lo que respecta a la precedencia cuando @@ method se usa en notación infija, tiene la más alta prioridad, lo que hace que sea más fácil razonar sobre lo que se registrará.

Entonces, ¿qué pasa si quieres utilizar un registrador diferente en uno de tus métodos? Esto es muy simple:

 import ExpressionLog.{logger=>_,_} // import everything but default logger // define specific local logger // this can be as simple as: implicit val logger = new MyLogger implicit val logger = new Logger { var lineno = 1 def info(s:String) { println("%03d".format(lineno) + ": " + s) lineno+=1 } } // start logging def sum (a:Int, b:Int) = a+b val c = "sum result" @@ sum("a" @@ 1, "b" @@2) 

Se producirá:

 001: a = 1 002: b = 2 003: sum result = 3 

Al comstackr todas las respuestas, pros y contras, se me ocurrió esto (el contexto es una aplicación Play):

 import play.api.LoggerLike object LogUtils { implicit class LogAny2[T](val value : T) extends AnyVal { def @@(str : String)(implicit logger : LoggerLike) : T = { logger.debug(str); value } def @@(f : T => String)(implicit logger : LoggerLike) : T = { logger.debug(f(value)) value } } 

Como puede ver, LogAny es un AnyVal por lo que no debe haber ninguna sobrecarga de creación de objetos nuevos.

Puedes usarlo así:

 scala> import utils.LogUtils._ scala> val a = 5 scala> val b = 7 scala> implicit val logger = play.api.Logger scala> val c = a + b @@ { c => s"result of $a + $b = $c" } c: Int = 12 

O si no necesita una referencia al resultado, solo use:

 scala> val c = a + b @@ "Finished this very complex calculation" c: Int = 12 

¿Alguna desventaja para esta implementación?

Editar:

Lo he hecho disponible con algunas mejoras en una esencia aquí