I often want to log or to print something without changing it.
It looks like this:
val result = myResult // this could be an Option or a Future
.map{r =>
info(s"the result is $r")
r
}
These three lines are always the same.
In a for comprehension, this can be done a bit nicer.
But I look for a solution for the first declarative version. It should look like:
val result = myResult
.log(info(s"the result is ${_}"))
This one-liner could be put in every place in the chain where there could be a map, like:
val result = myResult
.log(info(s"1. ${_}"))
.filter(_ > 1)
.log(info(s"2. ${_}"))
...
How could this be achieved? If possible, without a functional library.
Ok so I decided to take a swing at this and I would like to retract my comment of
Maybe you can define a implicit class that has a method "log" acting on a Product?
I was confident that Future and all the monads (collections, options) shared a common ancestor, turns out I was wrong. I have the following solution without using Cats. This can be done in a much prettier way in cats, besides the aforementioned " flatTap ", and embelished with possibly cats.Ref or something.
Future is the obvious outliner here but as more exceptions come along you might need to expand this object.
import scala.concurrent._
import ExecutionContext.Implicits.global
object MonadicConv {
implicit class MonadicLog[+B <: Product, A] (val u: B){
def log(l: String, args: List[A] = List()): B = {
println("logging")
println(u)
println(l)
u
}
}
implicit class FutureLog[T, A](val u: Future[T]){
def log(l: String, args: List[A] = List()) : Future[T] = {
println("logging")
println(u)
println(l)
u
}
}
}
1) You will need to modify this with you own logging logic, I am just printing
2) I am not super proud of this as this is no longer a pure function. I am not sure if there is a work around this in Scala without using Cats. (There might be)
3) The args can be removed, just added them in case you want to pass in extra info
4) If you really want to combine these, you could try defining your own product, some leads: Implement product type in Scala with generic update function working on its parts
You can use this with
import MonadicConv._
val x = Some(5).log("").get
val lx = List(Some(5), Some(10), Some(1)).log("list").flatten.log("x").filter(_ > 1).log("")
val ff = Future.successful(List(Some(5), Some(10), Some(1))).log("fff").map(_.flatten.filter(_ > 1).log("inner")).log("")
This prints out
logging
Some(5)
option test
logging
List(Some(5), Some(10), Some(1))
list test
logging
List(5, 10, 1)
flat test
logging
List(5, 10)
filter test
logging
Future(Success(List(Some(5), Some(10), Some(1))))
future test
logging
Future(<not completed>)
incomplete test
logging
List(5, 10)
inner future test
Scastie version here
As I have mentioned, this is really the Cats land at this point. This is the best I could come up with in core Scala
For your purpose, it is best to use treelog. It turns the logging process and values into a Monad of DescribedComputation
:
import treelog.LogTreeSyntaxWithoutAnnotations._
val result: DescribedComputation[YourValueType] = myResult ~> (_.fold("The result is empty")(r => s"The result is $r")
And usually to subtract the value from a DescribedComputation, use for comprehension:
for {
res <- result
} {
doSomethingTo(res)
}
See details from https://github.com/lancewalton/treelog
The whole example will look like:
val compRes = "Logging Result" ~< {
for {
r <- myResult ~> (_.fold("The result is empty")(r => s"The result is $r")
} yield r
}
}
for (res <- compRes) {
doSomethingTo(res)
}
logger.info(logging.run.written.shows)
The output will look like:
2019-11-18 00:00:00,000 INFO Logging Result
The result is XXX
Just for reference. ZIO provides this functionality nicely.
/**
* Returns an effect that effectfully "peeks" at the success of this effect.
*
* {{{
* readFile("data.json").tap(putStrLn)
* }}}
*/
final def tap[R1 <: R, E1 >: E](f: A => ZIO[R1, E1, Any]): ZIO[R1, E1, A] = self.flatMap(new ZIO.TapFn(f))
There is even a version for the error case:
/**
* Returns an effect that effectfully "peeks" at the failure of this effect.
* {{{
* readFile("data.json").tapError(logError(_))
* }}}
*/
final def tapError[R1 <: R, E1 >: E](f: E => ZIO[R1, E1, Any]): ZIO[R1, E1, A]
This makes debugging really easy:
myDangerousZioFunction
.tapError(e => putStrLn(s"Server Exception: $e"))
.tap(r => putStrLn(s"Result is $r"))
....
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.