分类 知乎专栏备份 下的文章

这篇文章依旧没有什么主题、没有什么深度,纯碎随笔,谈的也是 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()
  return retVal
}

而现在,仅靠标准库里的这几个方法,你就可以:

fun foo() = getResource().run { calculate().also { cleanUp() } }

标准库里的这几个方法仅仅是最最常用和最简单的对控制语句的补充。其实还有很多这样的扩展方法可以用,例如 funKTionale 里面的函数,都很棒呢。

v2-c72a92a564d55dd210ef2e640d0bd005_b.jpg

此文同时发布于 https://zhuanlan.zhihu.com/p/27073170

我注意到在我前些天的一篇回答的评论里,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 这两个东西,用在一起真是蛮合适呢。其实这个例子里,不加 sealeddata 两个修饰符,也能编译通过。但是,加上这两个修饰符,似乎更能表达类似于 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) == criteria)
            }.all { it }
        )
    }
}

inline fun <reified T: Any> match(vararg params: Any) = MatchDataClass(T::class, params)

于是,我们现在就可以写出跟上面的 Scala 代码有点像的 Kotlin 代码来:

when (foo) {
    in match<Bar>(any, "药丸") -> "后面有药丸"
    in match<Bar>("青果", any) -> { val (_, predicate) = foo; "青果" + predicate + "了" }
    else -> "啥都没有"
}

注意到,第二个 case 的代码比较恶心。这是因为 Scala 能够在 matching 的同时创建变量并赋值,而 Kotlin 再怎么 hack 也无法做到。幸而 Kotlin 还算支持 destructing。

当然,这样的写法有很多问题:

  • 用了反射,这样不仅慢,而且很脏
  • 编译器不会为你做类型检查,写出 match<Bar>(1, 2) 编译器也不会指出错误
  • 同样,exhaustiveness check 和 smart cast 就通通没法帮你了

所以,似乎我们还是应该期待 Kotlin 提供完整的 pattern matching 支持,才能发挥 sealed class 和 data class 的最大功力呢。

此文同时发布于 https://zhuanlan.zhihu.com/p/27050720

刚刚看 I/O 直播时听到 Kotlin gets officially supported,真的蛮意外的。

Kotlin 的 killer-app 大约就是用来写 Android app 的 Anko。从我初次用 Anko 到现在,已经过去快一年了。遗憾的是在之前的公司里,由于各种各样的原因,一直没法在线上产品中使用 Kotlin。

直到我上个月换了工作后——刚刚结束的四月里,使用 Anko,在不到一个月里就写完了两个漂亮的 Android App 并上线,生产力和舒适度实在前所未有。

当然,由于我很长时间里的主要在写 JavaScript,于是确实习惯了使用 Promise。我于是就简短地用 Kotlin 写了一个 Promise 实现—— https://github.com/kmxz/Votive/。API 尽可能地接近 JavaScript 中的 Promise;只是所有 Promise 的名称都改成了 Votive。

譬如,将 Android 的运行时权限申请封装为 Promise,只需要这么一点 boilerplating:

const val REQUEST_CODE_MIN = 6910
const val REQUEST_CODE_MAX = 7910

open class BaseActivity: AppCompatActivity() {

    private var lastRequestCode = REQUEST_CODE_MIN

    private var permissionCallback = mutableMapOf<Int, Pair<(Unit) -> Unit, ((Unit) -> Unit)>>()

    fun withPermission(permission: String) = Votive<Unit, Unit> { resolve, reject ->
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                val requestCode = lastRequestCode++
                if (lastRequestCode > REQUEST_CODE_MAX) { lastRequestCode = REQUEST_CODE_MIN }
                permissionCallbacks.put(requestCode, Pair(resolve, reject))
                ActivityCompat.requestPermissions(this, arrayOf(permission), requestCode)
            } else {
                resolve(Unit)
            }
        } else {
            resolve(Unit)
        }
    }

    final override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        if (requestCode in REQUEST_CODE_MIN..REQUEST_CODE_MAX) {
            val callback = permissionCallbacks.remove(requestCode)
            if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
                callback?.first?.invoke(Unit)
            } else { // already removed anyway
                callback?.second?.invoke(Unit)
            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        }
    }

}

就可以使用 Promise 风格去申请运行时权限:

withPermission(Manifest.permission.ACCESS_FINE_LOCATION)
    .thenSimple { Log.i(locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)) }
    .catchSimple { alert("Don't give me permission? I'll crash") }

嗯,以及我现在越来越觉得我一个月前辞职实在是绝赞的决定。

此文同时发布于 https://zhuanlan.zhihu.com/p/26966006

昨天刚刚交了今年的第一笔 Wikipedia 钱。

Screenshot_2016-04-03_13-59-11.png

说实话,Wikipedia 最伟大之处,在于它坚守了真正属于互联网的精神:开放、自由、免费。它不靠任何盈利组织进行运作,所有的内容都靠用户自愿贡献。它没有广告,没有任何盈利渠道,完全依赖捐款来支持。

这是一个乌托邦式的构想。但这样的构想显然成功了。这才是互联网。

在知乎的问题《微信公众号对比跟 RSS 有什么优势?》下,我曾经写到:

几年前有人抬举各种新的沟通工具,但是各种 IM 和「协作工具」死了一轮又一轮, 而邮件列表和 IRC 还是活得好好的。几年前有人说 blog 已死,抬举各种 microblog,但是微博活跃度已然下降,blog 依然有不少人在写。这是为什么?

为什么 HTTP 会战胜 Gopher?为什么 email 被叫做过时叫了 20 年,还是唯一不可或缺的网络沟通工具?为什么 Bitcoin 能够流行起来?

因为 Internet 自诞生以来就有着一种开放和去中心化的基因。任何试图将其中心化、试图使用封闭的协议的做法,都迟早会告失败和灭亡。

中国的那帮产品经理不过是历史中的烟尘;而 Aaron Swartz 将永生。

后来在另一个知乎问题下,我曾写下

Internet 终究从一个知识自由流通的乌托邦,沦为逐蝇头之利的城中村菜市场。

是的。破坏 Internet 环境的头号元凶是政府的干预和监管,第二大元凶便是商业化。「自由」作为 Tim Berners-Lee 创建 web 的本意,反而在这两座大山间被夹得喘不过气来。

这几年来,商业化让互联网变得令人作呕。

比如越来越多曾经功能简单而易用的网站和应用,为了盈利,加上越来越多无用而破坏体验的功能,强行将用户引导到那些垃圾上去。

比如只要有所谓的商业「梦想」,连 GPL 都能随意违反,甚至还有人出来为之洗地叫好。

比如越来越多的人竟然能够接受微信公众号这种糟糕的内容传播形式。它使用私有的协议和客户端,将用户局限在一个中心化的渠道上,完全没有自由和安全可言。

比如谈及「互联网」,越来越多的人竟然想到的不是如何无偿地共享自己的知识,而是试图用它牟利。

就连在知乎上——越来越多的人都把自己的内容标示为「禁止转载」,整天思考着「知乎大 V 如何变现」一类的问题。然后还要在回答末尾挂上自己微信公众号的二维码,活像是城中村电线杆上的小广告。

互联网精神岌岌可危。纵使无法阻挡商业化的浪潮,我也宁愿做那一个螳臂当车的歹徒。我坚信,开放、自由、免费、非盈利,才是互联网的根基所在。

我在互联网上的所有内容皆以 WTFPL 发布。接下来的一年里,我也决定:拒绝从任何广告链接购买任何产品;拒绝使用任何有侵入式广告的产品;对于所有试图将自己在 UGC 社区的活动或内容变现的人一律拉黑。

并且,每在国内互联网上消费一分钱,我将会给 EFFWikimedia FoundationCreative CommonsFSF 捐至少同等的数目。

此文同时发布于 https://zhuanlan.zhihu.com/p/20706679