为 Try[T] 制作了一个方便的日志记录功能,但因为类型系统而卡住了

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

我想知道这是否可以使用 Scala 的类型系统来完成。

基本上,我想创建一个日志记录方法,它接受类型为 Try[T]result 并根据 result 是否为 [=16] 打印出一条略有不同的消息=] 或 Failure

例如,签名可能看起来像

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

并且可以这样使用:

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

这将输出

size is 13 kb 如果 sizeSuccess(x: Int),例如 Success(13)

error: size is (not available) 如果 size 如果类型 Failure(t: Throwable)

棘手的部分是我们需要能够访问 Try 中的对象以便打印到屏幕(如果它是 Success,或者打印选定的默认字符串(例如“(不可用)") 如果它是 Failure 作为占位符。此外,它必须使用非常通用的类型 TAny,范围可以从简单的标量值到 class 的实例等

这个古怪函数的用例是,它将非常方便地以信息丰富的方式记录 Try 对象,而不会因 map / recover 或 match 语句而使代码混乱。

这是我想出的框架,当然最难的部分还没有弄清楚。

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

这就是我要做的(好吧,如果我不使用隐式将方法添加到 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}})"
}

编辑:了解您的要求后

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

我不知道实现泛型类型 T 的方法,但我认为这不是一个好主意:客户端可以在打印时特殊使用 T 的方法,但是您希望它始终在应该打印来自 T 的信息的地方打印 "not available",这并不那么简单。

另一种选择是创建一个特殊的格式化程序(想想 "size is ${...} kb"),这样记录器就会知道如何替换它,但我不确定这是否是您想要的。

这里是解决方案,它不强制您明确提供类型:

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

用作

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

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