Search code examples
scalacastingtype-conversionscala-generics

Making a convenient logging function for Try[T], but got stuck because of the type system


I wonder if this can be done using Scala's type system.

Basically, I want to make a logging method which accepts a result of type Try[T] and prints out a message that differs a bit depending whether result is a Success or a Failure

For example, the signature might look like

def logTry[T](what: Try[T], block: T => String): Unit

and can be used as such:

val size: Try[(Int, Int)] = Try(getSizeAndTimestampFromDatabase())
logTry(size, e => "size is " + e._2 + " kb")

which will output

size is 13 kb if size is Success(x: Int), e.g., Success(13)

or

error: size is (not available) if size if of type Failure(t: Throwable)

The tricky part is that we need to be able to access the object within the Try for printing to the screen if it's a Success, or print a chosen default string (e.g. "(not available)") if it's a Failure as the placeholder. Also, it must work with a very general type T or Any that can range from a simple scalar value to an instance of a class, etc.

The use case for this eccentric function is that it will be very handy for logging a Try object in an informative way, without cluttering the code with map / recover or a match statement.

This is the skeleton that I have came up with, but of course the hard part hasn't been figured out yet.

def logTry[T](what: Try[T], block: T => String): Unit = {
  what match {
    case Success(res) => println(block(res))
    case Failure(t) => println(???) // how to do this
  }
}

Solution

  • Here the solution, which not forces you to explicitly supply types:

    implicit class LogTry[T](attempt: Try[T]) {
      def log[E](comp: T => E, block: String => String, error: Throwable => String = _ => "(N/A)") =
        println(block(attempt map (comp andThen (_.toString)) recover { case ex => error(ex) } get))
    }
    

    use it as

    size.log(_._2, size => f"size is $size kb")
    

    or

    size.log(_._2, size => f"size is $size kb", err => f"(not available because $err)")