Born again shelter - swift
type: channel
link: https://bash.kmxz.net/tag/swift/
items: Kotlin 基础：从 null safety 到 Standard.kt
items: Data class 是好东西，Kotlin 就差一个 pattern matching 了

Kotlin 基础：从 null safety 到 Standard.kt
type: item
date: 2017-05-24T13:58:00+00:00
description: 这篇文章依旧没有什么主题、没有什么深度，纯碎随笔，谈的也是 Kotlin 最最基础的问题。我常常说 Kotlin 比 Swift 舒服的一点是，Kotlin 在 null-safety 问题上，会有所谓「smart cast」。例如你有var a: Foo? = getFoo() if (a != null) { print(a.foo) // 不再需要 !! } 而在 Swift 里，你仍然需要手动再次「!」var a: Foo? = getFoo() if (a != null) { print(a!.foo) // 仍然需要 ! } 当然所有人都会很快注意到的一点是，Kotlin 对于局部变量可以做这样的处理，但如果是个类的成员变量（var），就不行：class Bar { var a: Foo? = getFoo() fun bar() { if (a != null) { print(a.foo) // 编译无法通过 } } } 为什么 Kotlin 此时不予编译通过呢？因为在当前线程判断 a != null 之后，其它线程可能又修改了「a」的值。事实上，如果是用 val 声明的 field，此时就可以 smart cast。而如果是 var 声明的呢？如果你加上 !!，变成 a!!.foo，就可以编译通过，但这么做的话，你就会被抓住游街。所以，正确的解决方式是，定义一个局部变量，这样就确保没有其它线程能动到它。fun bar() { val localA = a if (localA != null) { print(localA.foo) // cool } } 这样写的唯一问题，就是丑。定义了一个可能只用到一次的局部变量，而它并不被整个方法所需要，而仅仅只是临时用到而已。大约 Swift 程序员们也意识到了这一点，所以 Swift 里习惯的 practice 是：（摘自官方教程）if let roomCount = john.residence?.numberOfRooms { print("John's residence has \(roomCount) room(s).") } else { print("Unable to retrieve the number of rooms.") } 也就是，为了写得更简明一些，Swift 从语法层面增加了 if let 语句解决问题。Kotlin 里，当然也可以利用 let 来写得漂亮些。当然，Kotlin 的 DSL 能力那么好，何必再从语法层面解决问题？print(john.residence?.numberOfRooms?.let { "John's residence has $it room(s)." } ?: "Unable to retrieve the number of rooms.") 一气呵成，不需要任何额外的语法。事实上，我实际写代码时，也经常会 foo?.let { … } 这么用。上面语句的核心就是 let。和 Swift 不同，Kotlin 里的 let 不过只是一个普通的函数而已。事实上，很多 Kotlin 教程都不会提及标准库里这几个很赞的函数。它们实现得非常简单，但却可以作为良好 Kotlin 代码的典范：public inline fun <T, R> T.run(block: T.() -> R): R = block() public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block() public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this } public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this } public inline fun <T, R> T.let(block: (T) -> R): R = block(this) public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null 熟悉它们，能够帮你大大地缩短代码。例如某个方法，在计算出返回值后，还需要做一些清理，之前你可能：fun foo(): Int { val resource = getResource() val retVal = resource.calculate() resource.cleanUp() retur…

Data class 是好东西，Kotlin 就差一个 pattern matching 了
type: item
date: 2017-05-22T15:54:00+00:00
description: 我注意到在我前些天的一篇回答的评论里，Swift 和 Scala 都被拿来和 Kotlin 做比较。我于是忽然想写一点没什么技术水平、也没什么内容结构的随笔，谈谈一些关于 data class 的一些有意思的地方。我写 Swift 时也注意到，Swift 里的 enum 中每一项是可以有参数的，亦即「associated values」。例如：enum Gender { case male case female case other(description: String) } 于是写 Kotlin 的时候，我就在想，为什么 Kotlin 的 enum 不能带参数呢…… 后来我才意识到，其实「带 associated values 的 enum」实际上可以用 sealed class 的语法来等同：sealed class Gender { object Male: Gender() object Female: Gender() data class Other(val description: String): Gender() } 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) 那么就可以对变量 foo: Bar 做foo match { case Bar(_, "药丸") => "后面有药丸" case Bar("青果", predicate) => "青果" + predicate + "了" case _ => "啥都没有" } 所以：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 // 因为没法直接用 _ class MatchDataClass<T: Any>(private val kClass: KClass<T>, private val params: Array<out Any>) { init { if (!kClass.isData) { throw IllegalArgumentException("Not a data class!") } } operator fun contains(input: Any): Boolean { // 实现「in」操作符 return if (!kClass.isInstance(input)) false else ( params.mapIndexed { index, criteria -> (criteria is any) || (kClass.java.getMethod("component" + (index + 1)).invoke(input) == crit…