loginscala时如何保持返回值

在java中编程时,我总是logging一个方法的input参数和返回值,但是在scala中,方法的最后一行是返回值。 所以我必须做一些事情:

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

为了使它容易,我写了一个实用程序:

 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 _ } 

然后我用它作为:

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

它会logging该值并将其返回。 它适用于我,但似乎更奇怪。 因为我是一个古老的java程序员,但是对于scala来说,我不知道在scala中是否有更习惯的方式来做到这一点。


感谢您的帮助,现在我使用romusz提到的Kestrel combinator来创build一个更好的util

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

我添加f参数,以便我可以从slf4j传递一个logging器,testing用例是:

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

你要找的是Kestrel combinator(K combinator): Kxy = x 。 您可以在返回传递给它的值的同时进行各种副作用操作(不仅是日志logging)。 阅读https://github.com/raganwald/homoiconic/blob/master/2008-10-29/kestrel.markdown#readme

在斯卡拉实现它的最简单的方法是:

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

那么你可以定义你的打印/loggingfunction:

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

并使用它:

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

你的例子函数变成一个单行的:

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

如果您更喜欢OO符号,则可以使用其他答案中显示的含义,但这种方法的问题是每次要logging某些内容时都会创build一个新对象,如果频繁执行,可能会导致性能下降。 但是为了完整性,它看起来像这样:

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

像这样使用它:

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

如果你更喜欢更通用的方法,你可以定义

 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 } 

并使用它

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

你的基本想法是正确的 – 你只需要把它整理一下,使其最大限度地方便。

 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) 

现在你可以

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

这样你就不需要重复自己(例如两次)。

假设您已经拥有所有logging器的基类:

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

然后你可以用@@ logging方法扩展String:

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

要使用日志logging,您必须导入ExpressionLog对象的成员,然后可以使用以下表示法轻松loggingexpression式:

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

将打印:

 a = 1 b = 2 sum result = 3 

这是有效的,因为每次当您在String编译器上调用@@方法时,都会意识到String没有方法并将其默默转换为具有定义了@@方法的匿名types的对象(请参阅stringToLog )。 作为转换的一部分,编译器会将所需的logging器作为一个隐含的参数来select ,这样,您不必每次都将logging器传递给@@而是完全控制每次需要使用哪个logging器。

就中文符号中使用@@方法而言,它的优先级最高 ,这使得更容易推断logging的内容。

那么如果你想在你的一个方法中使用不同的logging器呢? 这很简单:

 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) 

会输出:

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

编译所有的答案,利弊,我想出了这个(上下文是一个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 } } 

正如你所看到的,LogAny是一个AnyVal,所以不应该有任何新的对象创build的开销。

你可以像这样使用它:

 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 

或者,如果您不需要参考结果,只需使用:

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

这个实现的任何缺点?

编辑:

我在这里提供了一些改进