Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/scala/17.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
登录scala时如何保持返回值_Scala_Logging - Fatal编程技术网

登录scala时如何保持返回值

登录scala时如何保持返回值,scala,logging,Scala,Logging,在java编程时,我总是记录方法的输入参数和返回值,但在scala中,方法的最后一行是返回值。所以我必须做一些类似的事情: def myFunc() = { val rs = calcSomeResult() logger.info("result is:" + rs) rs } 为了方便起见,我编写了一个实用程序: class LogUtil(val f: (String) => Unit) { def logWithValue[T](msg: String, value

在java编程时,我总是记录方法的输入参数和返回值,但在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) 
它将记录该值并返回它。它对我很管用,但看起来很古怪。由于我是一名老java程序员,但对scala还是新手,我不知道在scala中是否有更惯用的方法来实现这一点


感谢您的帮助,现在我使用罗姆兹发明的Kestrel combinator创建了一个更好的UTI

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向它传递一个记录器,测试用例是:

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

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

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

这样,您就不需要重复自己的操作(例如,不重复两次)。

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

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

你要找的是一种叫做Kestrel combinator(K combinator):
Kxy=x
。您可以在返回传递给它的值时执行各种副作用操作(不仅仅是日志记录)。阅读

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

  def kestrel[A](x: A)(f: A => Unit): A = { f(x); x }
然后,您可以将打印/记录功能定义为:

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() = calcSomeResult().log("result is")
您的示例函数将变成一行:

def myFunc() = logging("result is", calcSomeResult())
如果您喜欢OO表示法,您可以使用隐式表示法,如其他答案所示,但这种方法的问题是,每次您想要记录某个内容时,都会创建一个新对象,如果您经常这样做,可能会导致性能下降。但就完整性而言,它看起来是这样的:

implicit def anyToLogging[A](a: A) = new {
  def log = logging(a)
  def log(msg: String) = logging(msg, a)
}
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
像这样使用它:

logging(1 + 2) + logging(3 + 4)
def myFunc() = calcSomeResult().log("result is")

假设您已经为所有日志记录者提供了一个基类:

abstract class Logger {
  def info(msg:String):Unit
}
然后可以使用
@
日志记录方法扩展字符串:

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
    }
  }
}
要使用日志记录,您必须导入
ExpressionLog
对象的成员,然后可以使用以下符号轻松记录表达式:

import ExpressionLog._

def sum (a:Int, b:Int) = "sum result" @@ (a+b)
val c = sum("a" @@ 1, "b" @@2)
将打印:

这是因为每次在
字符串
编译器上调用
@
方法时,都会意识到
字符串
没有该方法,并将其静默地转换为定义了
@
方法的匿名类型的对象(请参见
stringToLog
)。作为转换编译器的一部分,编译器会选择所需的记录器作为一个,这样您就不必每次都将记录器传递给
@
,但您仍然可以完全控制每次需要使用哪个记录器

在中缀表示法中使用
@
方法时,由于优先级较高,因此更容易对将记录的内容进行推理

那么,如果您想在其中一种方法中使用不同的记录器,该怎么办?这很简单:

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)
将输出:


综合所有答案和利弊,我得出了以下结论(上下文是一个游戏应用程序):

如您所见,LogAny是一个AnyVal,因此不应该有任何创建新对象的开销

您可以这样使用它:

implicit def anyToLogging[A](a: A) = new {
  def log = logging(a)
  def log(msg: String) = logging(msg, a)
}
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
这种实现有什么缺点吗

编辑:


我对Scala 2.13进行了一些改进,可以使用链接操作在返回原始值的同时对任何值应用副作用(在本例中是一些日志记录):

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

例如:

scala> val a = 42.tap(println)
42
a: Int = 42
或者在我们的情况下:

import scala.util.chaining._

def myFunc() = calcSomeResult().tap(x => logger.info(s"result is: $x"))

您可以使用相同数量的字符定义
点击
,而不是一个奇怪的符号,这在Ruby中的作用完全相同。谢谢,@RexKerr。我搜索了一些名称,但没有想到Ruby。请注意,在Scala 2.10中,您可以避免一些开销:
隐式类TapOnValue[a](val v:a)扩展AnyVal{…}
+1,以突出显示OO解决方案相对于等效函数增加的开销。我相信日志功能必须谨慎使用,在这种情况下,语法上的便利性可能优先于速度或内存的使用。感谢Kestrel combinator的知识,我现在可以有一个更好的工具,使它更容易接受产生任何值的函数并忽略它:
def Kestrel[a,B](x:a)(f:A=>B):A={f(x);x}
更好:
def kestrel[A](x:A)(f:A=>Any):A={f(x);x}
为什么不使用AspectJ?是的,它不是Scala,这是真的。遗憾的是,我们必须导入这个包才能使用它,因为这个特性非常有用。