Как я могу "превратить свою библиотеку" в Scala, чтобы она была ориентирована на будущее? - PullRequest
0 голосов
/ 14 мая 2018

Я использую неявные классы Scala для расширения объектов, с которыми я часто работаю. Как пример, у меня есть метод, подобный этому, определенный на Spark DataFrame:

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

Но неявные определения не вызываются, если класс уже определяет тот же метод. Что произойдет, если я позже обновлюсь до новой версии Spark, которая определяет метод DataFrame#deduplicate? Клиентский код автоматически переключится на новую реализацию, что может привести к незначительным ошибкам (или очевидным, которые менее проблематичны).

Используя отражение, я могу выдать ошибку runtime , если DataFrame уже определяет deduplicate до того, как мой неявный определяет это. Теоретически, если мой неявный метод конфликтует с существующим, я могу обнаружить его и переименовать в неявную версию. Однако после обновления Spark, запуска приложения и обнаружения проблемы уже слишком поздно использовать IDE для переименования старого метода, поскольку любые ссылки на df.deduplicate теперь ссылаются на собственную версию Spark. Мне пришлось бы вернуться к моей версии Spark, переименовать метод через IDE, а затем снова обновить. Не конец света, но не отличный рабочий процесс.

Есть ли лучший способ справиться с этим сценарием? Как можно безопасно использовать шаблон «pimp my library»?

Ответы [ 3 ]

0 голосов
/ 21 мая 2018

Вы можете добавить тест , который гарантирует, что определенные фрагменты кода не скомпилируют в набор тестов DataFrameExtension.Может быть, что-то вроде этого:

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

Если он компилируется без неявного преобразования, то это означает, что метод deduplicate был представлен библиотекой Spark.В этом случае тест не пройден, и вы знаете, что вам нужно обновить свои последствия.

0 голосов
/ 22 мая 2018

Решением для безопасной работы является явный запрос расширенного фрейма данных, чтобы минимизировать влияние, вы можете использовать неявный, чтобы иметь хороший синтаксис для преобразований (например, toJava / toScala и т. Д.):

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

И тогда ваш вызов будет выглядеть:

myDf.asExtended
  .deduplicate
  .someOtherExtensionMethod
  .andMore

Таким образом, вы можете проверить свои методы расширения на будущее без проверок во время выполнения / трюков / юнит-тестов (Вы даже можете использовать myDf.ext, это myDf.toExtended слишком долго :))

0 голосов
/ 14 мая 2018

Если метод расширения включен при импорте, используйте -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
  }
}
...