Подтвердить что ты не робот

Как сохранить возвращаемое значение при входе в систему scala

При программировании в 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, измеренный 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)}
}

Я добавляю параметр 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 called only once 
  val v = logV(logger.info)("result is", calcValue)
  v should be === 100
  }
}
4b9b3361

Ответ 1

То, что вы ищете, называется комбинатором Kestrel (комбинатор 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 }

Затем вы можете определить свою функцию печати/ведения журнала как:

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, вы можете использовать implicits, как показано в других ответах, но проблема с таким подходом заключается в том, что вы создадите новый объект каждый раз, когда хотите что-то зарегистрировать, что может привести к ухудшению производительности, если вы это сделаете достаточно часто. Но для полноты это выглядит так:

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

Используйте его как:

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

Ответ 2

Если вам нравится более общий подход, вы можете определить

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

Ответ 3

У вас есть основная идея: вам просто нужно немного поучить ее, чтобы сделать ее максимально удобной.

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

Таким образом, вам не нужно повторять себя (например, нет rs дважды).

Ответ 4

Скажем, у вас уже есть базовый класс для всех ваших регистраторов:

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

Затем вы можете расширить 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
    }
  }
}

Чтобы использовать регистрацию, вам необходимо импортировать элементы объекта ExpressionLog, а затем вы можете легко записывать выражения, используя следующие обозначения:

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 не имеет метода и бесшумно преобразует его в объект с анонимным типом, который имеет метод @@ (см. stringToLog). Как часть компилятора преобразования выбирает нужный регистратор как неявный параметр, таким образом, вам не нужно продолжать передавать логгеру @@ каждый раз, пока вы сохраняете полный контроль над тем, какой журнал должен использоваться каждый раз.

Что касается приоритета, когда метод @@ используется в нотации infix, он имеет самый высокий приоритет, что упрощает рассуждение о том, что будет регистрироваться.

Итак, что, если вы хотите использовать другой регистратор в одном из ваших методов? Это очень просто:

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

Ответ 5

Компиляция всех ответов, плюсов и минусов, я придумал это (контекст - приложение 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, поэтому не должно быть никаких накладных расходов на создание нового объекта.

Вы можете использовать его следующим образом:

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

Любые недостатки этой реализации?

Edit:

Я сделал это доступным с некоторыми улучшениями в здесь