Как я могу "развернуть свою библиотеку" с помощью Scala, ориентированной на будущее?
Я использую неявные классы Scala для расширения объектов, с которыми часто работаю. Как пример, у меня есть метод, подобный этому, определенный на Spark DataFrame
:
implicit class DataFrameExtensions(df: DataFrame) {
def deduplicate: Boolean =
df.groupBy(df.columns.map(col): _*).count
}
Но неявные определения не вызываются, если класс уже определяет тот же метод. Что произойдет, если я позже обновлюсь до новой версии Spark, которая определяет DataFrame#deduplicate
метод? Код клиента будет молча переключаться на новую реализацию, что может вызвать незначительные ошибки (или очевидные, которые менее проблематичны).
Используя отражение, я могу выдать ошибку времени выполнения, если DataFrame
уже определяет deduplicate
прежде чем мой неявный определяет это. Теоретически, если мой неявный метод конфликтует с существующим, я могу обнаружить его и переименовать в неявную версию. Однако после обновления Spark, запуска приложения и обнаружения проблемы уже слишком поздно использовать IDE для переименования старого метода, поскольку любые ссылки на df.deduplicate
Теперь обратитесь к родной версии Spark. Мне пришлось бы вернуть версию Spark, переименовать метод через IDE, а затем снова обновить. Не конец света, но не отличный рабочий процесс.
Есть ли лучший способ справиться с этим сценарием? Как можно безопасно использовать шаблон "pimp my library"?
3 ответа
Вы можете добавить тест, который гарантирует, что определенные фрагменты кода не компилируются в набор тестов DataFrameExtension
, Может быть, что-то вроде этого:
"(???: DataFrame).deduplicate" shouldNot compile
Если он компилируется без неявного преобразования, то это означает, что метод deduplicate
был представлен библиотекой Spark. В этом случае тест не пройден, и вы знаете, что вам нужно обновить свои последствия.
Если метод расширения включен при импорте, используйте -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
}
}
Решение для безопасной работы - явно запросить расширенный фрейм данных, чтобы минимизировать влияние, вы можете использовать неявный, чтобы иметь хороший синтаксис для преобразований (например, toJava / toScala и т. Д.):
implicit class DataFrameExtSyntax(df: DataFrame) {
def toExtended: DataFrameExtensions = DataFrameExtensions(df)
}
И тогда ваш вызов будет выглядеть так:
myDf.asExtended
.deduplicate
.someOtherExtensionMethod
.andMore
Таким образом, вы будете проверять свои методы расширения на будущее без проверок во время выполнения / трюков / трюков модульного тестирования (вы даже можете использовать myDf.ext
Это myDf.toExtended
это слишком долго:))