Как сохранить возвращаемое значение при входе в систему 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
}
}
Ответы
Ответ 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:
Я сделал это доступным с некоторыми улучшениями в здесь