登录scala时如何保持返回值

诺 铁*_*诺 铁 20 logging scala

在java中编程时,我总是记录输入参数和方法的返回值,但在scala中,方法的最后一行是返回值.所以我必须这样做:

def myFunc() = {
  val rs = calcSomeResult()
  logger.info("result is:" + rs)
  rs
}
Run Code Online (Sandbox Code Playgroud)

为了方便起见,我写了一个实用工具:

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 _
}
Run Code Online (Sandbox Code Playgroud)

然后我用它作为:

val rs = calcSomeResult()
withValue(logger.info)("result is:" + rs, rs) 
Run Code Online (Sandbox Code Playgroud)

它会记录该值并将其返回.它对我有用,但似乎很奇怪.因为我是一个古老的java程序员,但对scala不熟悉,我不知道在scala中是否有更惯用的方法.


感谢您的帮助,现在我使用romusz提到的Kestrel组合器创建了一个更好的工具

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)}
}
Run Code Online (Sandbox Code Playgroud)

我添加f参数,以便我可以从slf4j传递一个记录器,测试用例是:

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
  }
}
Run Code Online (Sandbox Code Playgroud)

rom*_*usz 34

您要查找的是所谓的红隼组合子(K组合子)Kxy = x.您可以在返回传递给它的值时执行各种副作用操作(不仅仅是记录).阅读https://github.com/raganwald/homoiconic/blob/master/2008-10-29/kestrel.markdown#readme

在Scala中,实现它的最简单方法是:

  def kestrel[A](x: A)(f: A => Unit): A = { f(x); x }
Run Code Online (Sandbox Code Playgroud)

然后,您可以将打印/日志记录功能定义为:

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

并使用它像:

logging(1 + 2) + logging(3 + 4)
Run Code Online (Sandbox Code Playgroud)

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

def myFunc() = logging("result is", calcSomeResult())
Run Code Online (Sandbox Code Playgroud)

如果您更喜欢OO表示法,您可以使用其他答案中显示的含义,但这种方法的问题在于,每次要记录某些内容时都会创建一个新对象,如果经常这样做,可能会导致性能下降.但为了完整性,它看起来像这样:

implicit def anyToLogging[A](a: A) = new {
  def log = logging(a)
  def log(msg: String) = logging(msg, a)
}
Run Code Online (Sandbox Code Playgroud)

使用它像:

def myFunc() = calcSomeResult().log("result is")
Run Code Online (Sandbox Code Playgroud)

  • +1用于突出显示OO解决方案的额外开销,与功能等效项相比较.我相信必须谨慎使用日志记录功能,在这种情况下,语法方便性可能优先于速度或内存使用. (3认同)

Deb*_*ski 7

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

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
}
Run Code Online (Sandbox Code Playgroud)

并使用它

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

calcSomeResult() tap println
Run Code Online (Sandbox Code Playgroud)

  • 你可以在相同数量的字符中定义`tap`,而不是一个奇怪的符号,它在Ruby中完全相同. (6认同)
  • 请注意,在Scala 2.10中,您可以[值类](http://docs.scala-lang.org/overviews/core/value-classes.html)来避免一些开销:`隐式类TapOnValue [A](val v: A)扩展AnyVal {...}` (2认同)

Rex*_*err 6

你有基本的想法 - 你需要整理一下才能使它最方便.

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)
Run Code Online (Sandbox Code Playgroud)

现在你可以

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

这样你就不需要重复自己(例如没有rs两次).


Xav*_*hot 6

从 开始Scala 2.13,链接操作tap可用于在返回原始值的同时对任何值应用副作用(在本例中为一些日志记录):

def tap[U](f: (A) => U): A

例如:

scala> val a = 42.tap(println)
42
a: Int = 42
Run Code Online (Sandbox Code Playgroud)

或者在我们的例子中:

import scala.util.chaining._

def myFunc() = calcSomeResult().tap(x => logger.info(s"result is: $x"))
Run Code Online (Sandbox Code Playgroud)