標籤:

在 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 類有著兩個明顯的缺陷:

  1. 沒有包含標記信息,無法跳出指定標籤的函數
  2. 作為異常需要填充異常棧,但我們並不需要這個信息,所以就白白浪費了大量時間

第一個問題很容易就能夠解決,我們需要用另一個欄位來保存標籤信息。為了方便使用模式匹配,我們把這個異常類定義為 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

源代碼見:github.com/Glavo/Sotlin


推薦閱讀:

如何評價 scala native?
有哪些值得推薦的 Scala/Spark 編程 IDE?
Scala中何時應當使用Var變數?
scala中web開發框架,哪一個能最後一統scala天下?lift or play

TAG:Dotty | Scala | Kotlin |