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

95 Views Asked by At

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
  }
}
2

There are 2 best solutions below

1
On BEST ANSWER

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)")
6
On

This is what I would do (well, if I didn't use implicits to add the method to Try):

def logTry[T](t: Try[T])(f: Try[T] => String) { println(f(t)) }

val size: Try[Integer] = Try(getSizeFromDatabase())
logTry(size) {
    case Success(e) => s"size is $e kb"
    case Failure(e) => s"error: size is (${e.getMessage}})"
}

Edit: after understanding your request

def logTry(t: Try[Any], f: Any => String) {
    t match {
        case Success(e) => println(f(e))
        case Failure(e) => println(f(new Object {
            override def toString = "(not available)"
        }))
    }
}

val size: Try[Integer] = Try(getSizeFromDatabase())
logTry(size, s => s"Size is $s kb")

I don't know of a way to achieve for a generic type T, but I don't think that's a good idea anyway: it's possible for the client to make special use of the methods of T when printing, but you want it to always print "not available" where it was supposed to print the information from T, and that's not so simple.

Another option is to create a special formatter (think "size is ${...} kb") so the logger would know how to replace it, but I'm not sure if this is what you want.