在 Dotty 中模擬 Kotlin (1) —— 標籤返回
Kotlin 是一種與 Scala 類似,並且能夠編譯到 Java 位元組碼的靜態類型語言。藉助 Scala 2.x 中以及存在的語法以及 Dotty 中新的語法,我們能夠輕鬆的在 Dotty 中模擬 Kotlin。
本系列文章講述的內容是 Glavo/Sotlin 的一部分,歡迎參觀
本文將會介紹如何在 Dotty 中模擬 Kotlin 的標籤返回(Return at Labels)。
背景
我們知道,Kotlin 中的 return 語句支持一種叫做標籤返回的方式,我們可以這樣:
fun foo() {n val ints = listOf(20, 15, 30, 0, 100)n ints.forEach lit@ {n if (it == 0) return@litn println(it)n }n}n
調用時的輸出:
>>> foo()n20n15n30n
return@lit 能夠直接返回 forEach 方法到 foo 函數中。這種靈活的方式能夠幫助我們書寫更加靈活的代碼。
Scala 中我們能不能模擬這種寫法呢?答案是肯定的,我們最後能夠實現一個這樣的 foo :
def foo(): Unit = {n val ints = List(20, 15, 30, 0, 100)n ints.foreach.tag(lit) { it =>n if(it == 0) returnUnit(lit)n println(it)n }n} n
保存返回值信息
非局部返回是一種破壞了我們通常的控制流程的操作。怎樣才能夠破壞正常的函數返迴流程呢?我們很容易想到通過拋出異常來實現。因為我們需要返回一個值,所以這個異常類中應該儲存有我們需要返回的值。考慮這些,我們定義了一個 ReturnControl 類:
class ReturnControl[A](val returnValue: A) extends Exceptionn
但是這個最初版本的 ReturnControl 類有著兩個明顯的缺陷:
- 沒有包含標記信息,無法跳出指定標籤的函數
- 作為異常需要填充異常棧,但我們並不需要這個信息,所以就白白浪費了大量時間
第一個問題很容易就能夠解決,我們需要用另一個欄位來保存標籤信息。為了方便使用模式匹配,我們把這個異常類定義為 case 類:
case class ReturnControl[A](tag: Symbol, value: A) extends Exceptionn
在這裡,我們用 Symbol 類型的值保存標籤信息,這樣在使用時能夠用類似 lit 的語法來傳遞標籤。
對於第二個問題,Scala 標準庫提供了 scala.util.control.ControlThrowable 特質,這個特質混入了 scala.util.control.NoStackTrace 特質。NoStackTrace 特質重寫了 Throwable 類的 fillInStackTrace 方法,在創建時不會對異常棧進行填充,提高了運行效率。
最後,我們的 ReturnControl 類變成了這樣:
case class ReturnControl[A](tag: Symbol, value: A) extends ControlThrowablen
為函數添加標籤
Scala 中的函數本身是沒有標籤功能的,所以我們需要為函數拓展出 tag 方法來為函數添加標籤,並且在捕獲到包含對應標籤的 ReturnControl對象時跳出函數:
implicit class ImplF1[A, R](val f: (A) => R) extends AnyVal {n def tag(tag: Symbol): (A) => R = (a) => {n try f(a) catch {n case ReturnControl(`tag`, v: R) => vn }n }n}n
因為 Dotty 中實現了自動η擴張(Automatic Eta Expansion),所以 foreach 等方法也會自動被視為函數。
因為隱式類必須是內部類,所以我們把這個隱式類放在叫做 Return 的 object 中,使用時需要導入 Return 的 ImplF1 成員。
添加幫助方法
我們已經基本完成了標籤返回,但是現在的標籤返回的使用方式很不自然:
def foo(): Unit = {n val ints = List(20, 15, 30, 0, 100)n ints.foreach.tag(lit) { it =>n if(it == 0) throw ReturnControl(lit, ())n println(it)n }n} n
為了能夠更加自然的使用標籤返回語法,我們在 Return 對象中添加一些輔助方法。最後我們的 Return 是這個樣子:
object Return {n def apply[R](tag: Symbol)(value: R): Nothing =n throw ReturnControl(tag, value)nn def returnUnit(tag: Symbol): Nothing = n throw ReturnControl(tag, ())nn def returnNull(tag: Symbol): Nothing = n throw ReturnControl(tag, null)nn implicit class ImplF0[R](val f: () => R) extends AnyVal {n def tag(tag: Symbol): () => R = () => {n try f() catch {n case ReturnControl(`tag`, v: R) => vn }n }n }nn implicit class ImplF1[A, R](val f: (A) => R) extends AnyVal {n def tag(tag: Symbol): (A) => R = (a) => {n try f(a) catch {n case ReturnControl(`tag`, v: R) => vn }n }n }n}n
源代碼見:https://github.com/Glavo/Sotlin/blob/master/src/main/scala/org/glavo/kotlin/control/Return.scala
推薦閱讀:
※如何評價 scala native?
※有哪些值得推薦的 Scala/Spark 編程 IDE?
※Scala中何時應當使用Var變數?
※scala中web開發框架,哪一個能最後一統scala天下?lift or play