[英]How can I “pimp my library” with Scala in a future-proof way?
我使用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重命名該方法,然后再次升級。 不是世界末日,而是一個偉大的工作流程。
有沒有更好的方法來處理這種情況? 如何安全地使用“皮條客我的圖書館”模式?
您可以添加一個測試,確保某些代碼段不會編譯到DataFrameExtension
的測試套件中。 也許是這樣的:
"(???: DataFrame).deduplicate" shouldNot compile
如果它在沒有隱式轉換的情況下編譯,則意味着Spark庫引入了方法deduplicate
。 在這種情況下,測試失敗,您知道必須更新您的implicits。
如果導入啟用了擴展方法,請使用-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
這樣你就可以在沒有運行時檢查/ linting /單元測試技巧的情況下對未來的擴展方法進行驗證(你甚至可以使用myDf.ext
因為myDf.toExtended
太長了:))
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.