Data class 是好東西,Kotlin 就差一個 pattern matching 了
enum Gender {n case malen case femalen case other(description: String)n}n
sealed class Gender {n object Male: Gender()n object Female: Gender()n data class Other(val description: String): Gender()n}n
Sealed class 和 data class 這兩個東西,用在一起真是蠻合適呢。其實這個例子里,不加「sealed」和「data」兩個修飾符,也能編譯通過。但是,加上這兩個修飾符,似乎更能表達類似於 enum 的語義。為什麼呢?
在 Kotlin 里,sealed class 也是為了協助 when 語句的 exhaustiveness check 的;也就是說,對於一個 sealed class,如果 when 已經處理了它的所有已知子類,那就不再需要 else 分支。事實上,Sealed class 這個概念在 Scala 里也有完全相同的存在——相似地,它的主要用途是協助 pattern matching 的 exhaustiveness check。而 Scala 里也有著和 data class 相似的存在,那就是 case class。它們同樣用於表示「僅僅用於組織數據的類」,也就是類似於「帶了 tags 的 tuple」;並且,它們同樣提供了符合直覺的 equals 方法的實現。
然而,在 Scala 里,正如「case class」這個名字所暗示,其一個很大的用途是在 pattern matching 中。在 Scala 中,假設有case class Bar(noun: String, verb: String)n
foo match {n case Bar(_, "藥丸") => "後面有藥丸"n case Bar("青果", predicate) => "青果" + predicate + "了"n case _ => "啥都沒有"n}n
所以:Kotlin 不提供 pattern matching 簡直就是反人類!
Improved Pattern Matching in Kotlin 提供了一些增強 when 語句的奇技淫巧。當然,這樣的奇技淫巧的代價,就是喪失 exhaustiveness check 的功能——因為 when 語句的 exhaustiveness check 非常弱,僅僅能識別「is XXX」這樣的條件。利用類似的奇蹟淫巧,再加上更骯髒的反射,我們就能對 Kotlin 的 data class 做類似的事情——至少實現上面那段 Scala 中那樣,對 foo 中的屬性的值做 matching。這裡利用的是,Kotlin 對於 data class 會生成 componentN() 方法 。「奇技淫巧」所需要的一點 bolterplating:object any // 因為沒法直接用 _nnclass MatchDataClass<T: Any>(private val kClass: KClass<T>, private val params: Array<out Any>) {n init {n if (!kClass.isData) { throw IllegalArgumentException("Not a data class!") }n }n operator fun contains(input: Any): Boolean { // 實現「in」操作符n return if (!kClass.isInstance(input)) false else (n params.mapIndexed { index, criteria ->n (criteria is any) || (kClass.java.getMethod("component" + (index + 1)).invoke(input) == criteria)n }.all { it }n )n }n}nninline fun <reified T: Any> match(vararg params: Any) = MatchDataClass(T::class, params)n
於是,我們現在就可以寫出跟上面的 Scala 代碼有點像的 Kotlin 代碼來:
when (foo) {n in match<Bar>(any, "藥丸") -> "後面有藥丸"n in match<Bar>("青果", any) -> { val (_, predicate) = foo; "青果" + predicate + "了" }n else -> "啥都沒有"n}n
- 用了反射,這樣不僅慢,而且很臟
- 編譯器不會為你做類型檢查,寫出 match<Bar>(1, 2) 編譯器也不會指出錯誤
- 同樣,exhaustiveness check 和 smart cast 就通通沒法幫你了
所以,似乎我們還是應該期待 Kotlin 提供完整的 pattern matching 支持,才能發揮 sealed class 和 data class 的最大功力呢。
推薦閱讀:
※#scala#模式匹配
※【太閣x周刊】第十一期:紐約線下活動預告、技術討論群熱點話題、Scala中的Typeclass模式實例、人氣文章
※提議:在Dotty 中使用縮進語法
※Stack monads in Scala
※Scala快速入門-6-單例對象及伴生對象