【问题标题】:Is it possible to pass "this" as implicit parameter in Scala?是否可以在 Scala 中将“this”作为隐式参数传递?
【发布时间】:2010-11-24 17:18:28
【问题描述】:

假设我想用一个记录异常并继续的 try-catch 块来包装可以抛出异常的代码。比如:

loggingExceptions {
  // something dangerous
}

理想情况下,我想用于记录调用对象上定义的 Logger(如果有的话)(如果没有,则会出现编译时错误)。我很想定义这样的东西:

def loggingExceptions[L <: { def logger: Logger }](work: => Unit)(implicit objectWithLogger: L): Unit = {
  try {
    work
  } catch {
    case t: Exception => objectWithLogger.logger.error(t.getMessage)
  }
}

objectWithLogger 会以某种方式“神奇地”扩展为客户端代码中的“this”。这(或类似的事情)可能吗?

【问题讨论】:

    标签: scala implicit


    【解决方案1】:

    实际上可以随心所欲地完成。其他回答者投降的太快了。没有白旗!

    package object foo {
      type HasLogger = { def logger: Logger }
      implicit def mkLog(x: HasLogger) = new {
        def loggingExceptions(body: => Unit): Unit =
          try body
          catch { case ex: Exception => println(ex) }
      }
    }
    
    package foo {
      case class Logger(name: String) { }
    
      // Doesn't compile:
      // class A {
      //   def f = this.loggingExceptions(println("hi"))
      // }
      // 1124.scala:14: error: value loggingExceptions is not a member of foo.A
      //         def f = this.loggingExceptions(println("hi"))
      //                      ^
      // one error found  
    
      // Does compile
      class B {
        def logger = Logger("B")
        def f = this.loggingExceptions(println("hi"))
        def g = this.loggingExceptions(throw new Exception)
      }
    }
    
    object Test {
      def main(args: Array[String]): Unit = {
        val b = new foo.B
        b.f
        b.g
      }
    }
    
    // output
    //
    // % scala Test
    // hi
    // java.lang.Exception
    

    【讨论】:

    • +1 为例。这可以做到,但不可取。它没有将日志记录问题与 Logging 特征分开,需要“隐式魔法”,并且会产生不必要的编译时(隐式搜索)和运行时(反射)开销。该实现隐藏了隐式转换需要在范围内的事实,并且在更现实的代码库中,这将需要客户端代码中的导入语句。
    • 您的否定清单总结为“我宁愿解决不同的问题”。嗯,是的,我们不是吗?
    • 并非如此。基本问题是在具有记录器的类中的代码块中记录异常。您可以按照 OP 最初设想的方式(使用隐式)解决该问题,或者您可以使用 trait 更简单地解决它。
    • 我刚刚重新阅读了这个问题。也许日志记录用例只是获得更通用技术的一个激励示例? 傻笑
    • @Aaron 日志记录案例是我要解决的实际问题,我对 trait 解决方案很满意。但了解如何“模拟”这种“隐含的替代品”如何工作也很有用。
    【解决方案2】:

    Debilski's answer 会起作用,但我不确定我是否有充分的理由在这里使用结构类型(即{ def logger: Logger })。每当调用logger 时,这样做都会产生额外的运行时开销,因为结构类型的实现依赖于反射。 loggingExceptions 方法与日志记录密切相关,所以我只想将其作为 Logging 特征的一部分:

    trait Logging {
       def logger: Logger
    
       final def loggingExceptions(body: => Unit) =
          try body catch { case e: Exception => logger.error(e.getMessage) }
    }
    
    trait ConcreteLogging extends Logging { 
       val logger = // ...
    }
    
    object MyObject extends SomeClass with ConcreteLogging {
       def main {
          // ...
          loggingExceptions { 
             // ...
          }
       }
    }
    

    【讨论】:

      【解决方案3】:

      您可以为所有想要使用def loggingExceptions 的类添加一个特征,并在这个特征中添加一个期望def logger: Logger 可用的自我类型。

      trait LoggingExceptions {
          this: { def logger: Logger } =>
        def loggingExceptions(work: => Unit) {
          try { work }
          catch { case t: Exception => logger.error(t.getMessage) }
        }
      }
      
      object MyObjectWithLogging extends OtherClass with LoggingExceptions {
        def logger: Logger = // ...
      
        def main {
          // ...
          loggingExceptions { // ...
          }
        }
      }
      

      【讨论】:

      • 谢谢,这行得通!但是是否有任何其他解决方案不涉及更改所有希望使用 loggingExceptions(...) 的类的声明?
      • @JPP 不,至少调用站点需要在范围内具有预期类型的​​隐式对象。例如,您可以将隐式参数设为Logger,并将调用对象中的def logger 更改为implicit def logger。但是,除非必要,否则应避免使用隐式,并且 trait 非常适合此问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-11
      • 1970-01-01
      • 2018-06-03
      • 2010-10-05
      • 2023-03-09
      相关资源
      最近更新 更多