我怎样才能让我的图书馆和#34;使用Scala以未来的方式?

时间:2018-05-14 13:15:35

标签: scala implicit implicits scala-implicits

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

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

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

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

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

3 个答案:

答案 0 :(得分:6)

您可以在DataFrameExtension的测试套件中添加test that ensures that certain code snippets do not compile。也许是这样的:

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

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

答案 1 :(得分: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
  }
}

答案 2 :(得分:0)

安全地执行此操作的解决方案是明确要求扩展数据框,以最小化影响,您可以使用隐式语法来获得良好的转换语法(例如toJava / toScala等):

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

然后你的调用将会显示:

myDf.asExtended
  .deduplicate
  .someOtherExtensionMethod
  .andMore

这样,您可以在没有运行时检查/ linting /单元测试技巧的情况下为您的扩展方法提供面向未来的方法 (您甚至可以使用myDf.ext myDf.toExtended太长了:))