關於Kotlin語法的異議
情況
Lambda的表示法是{ ... } ,例如:
val func = { println()}val func = { x -> println(x)}
若函數的唯一或最後一個參數是函數類型,可以不需要用括弧圍住這個參數,這樣就能隨手寫出這樣漂亮的DSL:
// transaction(...)接受一個類型為函數的參數// Auto handle a transactiontransaction { saveData()}// use(...)接受一個類型為函數的參數// Auto close the resourceinputStream.use { consume(it)}
但是就不能像C/Java/Scala一樣用花括弧把代碼組織成代碼塊了,而是必須調用run函數,這是Kotlin開發組的一種取捨:
// JavaString externalName;{ String internalName = getInternalName(); externalName = convert(internalName);}// Scalaval externalName = { val internalName = getInternalName() convert(internalName)}// Kotlinval externalName = run { val internalName = getInternalName() convert(internalName)}
run函數會被編譯器內聯,沒有函數調用的額外開銷,只是語法上與C家族不一致,而且有點繁瑣。
類似run的函數有好幾個,我們來瞧一瞧。
run(block: () -> R): R = block()
執行block,返回block的結果R,相當於Scala的普通代碼塊
T.run(block: T.() -> R): R = block()
把T作為this,執行block,返回block的結果R
T.let(block: (T) -> R): R = block(this)
把T作為block的入參(it),執行block,返回block的結果R
T.apply(block: T.() -> Unit): T { block(); return this }
把T作為this,執行block,返回T本身
T.also(block: (T) -> Unit): T { block(this); return this }
把T作為block的入參(it),執行block,返回block的結果R
with(receiver: T, block: T.() -> R): R = receiver.block()
把第一個參數作為this傳給block。能表達這種代碼:with(entry) { consumeKeyValue(key, value) }
當我們想隨手轉換一個對象,可以這麼寫:
val userDTO = user.run { UserDTO(name, password)}
或這麼寫
val userDTO = user.let { UserDTO(it.name, it.password)}
當我們新建了一個對象,想隨手給它完成初始化,可以這麼寫:
val user = User().apply { name = "Mr Wang" password = "&%&&**("}
或這麼寫
val user = User().let { it.name = "Mr Wang" it.password = "&%&&**("}
你可能覺得這只是微小的語法糖,那麼看這個nullable的例子:
// 繁瑣寫法val userDTO = if (user != null) { UserDTO(user.name, user.password) } else { null }// 簡潔寫法val userDTO = user?.run { UserDTO(name, password)}
是不是高下立見?
批評
為了支持多種寫法,佔用了很多名字。像run這種在JDK中常見的名字也被用了,雖然有靜態檢查,但同一個名字被安上不同的語義仍然是一種心智負擔。況且run這個名字完全沒有表達「轉換」的意思嘛!
使用函數之前要先想好接下來的寫法適合哪個名字的函數,也是一種心智負擔。
為了使語義更明確,我建議做如下改進:
減少內置函數名:去掉run和also,只保留let,apply和with。採用如下幾種函數簽名:
let(block: () -> R)
T.let(block: T.() -> R) T.let(block: (T) -> R): R
T.apply(block: T.() -> Unit): T T.apply(block: (T) -> Unit): T
with(receiver: T, block: T.() -> R): R
如上,with沒有變,run被併入let,also被併入apply。
let表示定義新的變數,apply表示對現有變數做一些處理,with表示以現有變數作為scope來做一些事(包括返回新的變數)。語義很清楚。
let總是返回結果R,apply總是返回原本的T。let/apply若接受無參函數,就把T作為this,若接受唯一參數為T的函數,就把T作為參數傳入,不允許隱式的it參數。
// 使用thisuser.apply { name = "Mr Wang" password = "&%&&**("}// 使用it必須顯式聲明user.apply { it -> it.name = "" it.password = "&%&&**("}
另一個問題,從Java轉過來的應用開發者可能會習慣性地寫一個代碼塊,忘了這實際上是一個Lambda,是不會執行的。
// 不會執行{ doSomething()}// 會執行{ doSomething()}()
原則上應禁止定義未被使用的Lambda,以免誤寫出永遠不會被執行的代碼。
推薦閱讀:
※Android 坑檔案:背黑鍋的 Fragment
※如何評價開中文C# issue的人到kotliner.cn論壇提中文版Kotlin?
※Android 開發者自述:為什麼我要改用 Kotlin?
※Kotlin 互動式命令行工具:kotlinc
※從零開始自製遊戲引擎 IV
TAG:Kotlin |