11

我使用 Scala 隐式类来扩展我经常使用的对象。例如,我有一个类似于 Spark 上定义的方法DataFrame

implicit class DataFrameExtensions(df: DataFrame) {
  def deduplicate: Boolean = 
    df.groupBy(df.columns.map(col): _*).count
}

但是如果类已经定义了相同的方法,则不会调用隐式定义。如果我稍后升级到定义DataFrame#deduplicate方法的新版本 Spark 会发生什么?客户端代码将默默地切换到新的实现,这可能会导致细微的错误(或明显的错误,问题较少)。

使用反射,如果在我的隐式定义之前已经定义,我可以抛出运行时错误。然后,理论上,如果我的隐式方法与现有方法冲突,我可以检测到它并重命名我的隐式版本。但是,一旦我升级 Spark、运行应用程序并检测到问题,使用 IDE 重命名旧方法为时已晚,因为现在对本机 Spark 版本的任何引用。我必须恢复我的 Spark 版本,通过 IDE 重命名方法,然后再次升级。不是世界末日,但不是一个伟大的工作流程。DataFramededuplicatedf.deduplicate

有没有更好的方法来处理这种情况?如何安全地使用“pimp my library”模式?

4

3 回答 3

6

您可以添加一个测试,以确保某些代码片段不会编译DataFrameExtension. 也许是这样的:

"(???: DataFrame).deduplicate" shouldNot compile

如果它在没有你的隐式转换的情况下编译,则意味着该方法deduplicate已被 Spark 库引入。在这种情况下,测试失败,并且您知道您必须更新您的隐式。

于 2018-05-21T18:04:17.133 回答
2

如果扩展方法由导入启用,使用-Xlint表示不再使用导入:

//class C
class C { def x = 17 }

trait T {
  import Extras._
  def f = new C().x
}

object Extras {
  implicit class X(val c: C) {
    def x = 42
  }
}

另一种观点,证据必须在以下情况下使用-Xlint -Xfatal-warnings

//class C[A]
class C[A] { def x = 17 }

trait T {
  import Mine.ev
  val c = new C[Mine]
  def f = c.x
}

trait Mine
object Mine {
  implicit class X[A](val c: C[A]) {
    def x(implicit @deprecated("unused","") ev: Mine) = 42
  }
  implicit val ev: Mine = null
}

object Test {
  def main(args: Array[String]): Unit = println {
    val t = new T {}
    t.f
  }
}
于 2018-05-14T19:17:07.127 回答
0

安全地执行此操作的解决方案是显式请求扩展数据帧,以最大程度地减少影响,您可以使用隐式来获得一个很好的转换语法(如 toJava/toScala 等):

implicit class DataFrameExtSyntax(df: DataFrame) { 
 def toExtended: DataFrameExtensions = DataFrameExtensions(df)
}

然后您的调用将如下所示:

myDf.asExtended
  .deduplicate
  .someOtherExtensionMethod
  .andMore

这样你就可以在没有运行时检查/linting/单元测试技巧的情况下对扩展方法进行未来验证(你甚至可以使用myDf.extmyDf.toExtended太长:))

于 2018-05-22T17:11:39.633 回答