When programming in java, I always log input parameter and return value of a method, but in scala, the last line of a method is the return value. so I have to do something like:
def myFunc() = {
val rs = calcSomeResult()
logger.info("result is:" + rs)
rs
}
in order to make it easy, I write a utility:
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 _
}
Then I used it as:
val rs = calcSomeResult()
withValue(logger.info)("result is:" + rs, rs)
it will log the value and return it. it works for me,but seems wierd. as I am a old java programmer, but new to scala, I don't know whether there is a more idiomatic way to do this in scala.
thanks for your help, now I create a better util using Kestrel combinator metioned by 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)}
}
I add f parameter so that I can pass it a logger from slf4j, and the test case is:
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
}
}
Compiling all the answers, pros and cons, I came up with this (context is a Play application):
As you can see, LogAny is an AnyVal so there shouldn't be any overhead of new object creation.
You can use it like this:
Or if you don't need a reference to the result, just use:
Any downsides to this implementation?
Edit:
I've made this available with some improvements in a gist here
Let's say you already have a base class for all you loggers:
Then you could extend String with the
@@
logging method:To use the logging you'd have to import members of
ExpressionLog
object and then you could easily log expressions using the following notation:Will print:
This works because every time when you call a
@@
method on aString
compiler realises thatString
doesn't have the method and silently converts it into an object with anonymous type that has the@@
method defined (seestringToLog
). As part of the conversion compiler picks the desired logger as an implicit parameter, this way you don't have to keep passing on the logger to the@@
every time yet you retain full control over which logger needs to be used every time.As far as precedence goes when
@@
method is used in infix notation it has the highest priority making it easier to reason about what will be logged.So what if you wanted to use a different logger in one of your methods? This is very simple:
Will output:
If you like a more generic approach better, you could define
and use it like
What you're looking for is called Kestrel combinator (K combinator):
Kxy = x
. You can do all kinds of side-effect operations (not only logging) while returning the value passed to it. Read https://github.com/raganwald/homoiconic/blob/master/2008-10-29/kestrel.markdown#readmeIn Scala the simplest way to implement it is:
Then you can define your printing/logging function as:
And use it like:
your example function becomes a one-liner:
If you prefer OO notation you can use implicits as shown in other answers, but the problem with such approach is that you'll create a new object every time you want to log something, which may cause performance degradation if you do it often enough. But for completeness, it looks like this:
Use it like:
You have the basic idea right--you just need to tidy it up a little bit to make it maximally convenient.
Now you can
This way you don't need to repeat yourself (e.g. no
rs
twice).Starting
Scala 2.13
, the chaining operationtap
can be used to apply a side effect (in this case some logging) on any value while returning the original value:For instance:
or in our case: