關於 Kotlin REPL 的兩條小貼士

jywhltj發表於2017-09-01

Kotlin 自帶了互動式程式設計命令列,即 REPL(Read-Eval-Print Loop 的簡寫,直譯為 “讀取-求值-輸出”迴圈),尤其適合快速實驗一些東西。 本文只講關於 Kotlin REPL 的兩條 tips:

  1. 如何執行 REPL;
  2. 如何在 REPL 中檢視推斷出的型別。

如何執行 Kotlin REPL

執行 Kotlin REPL 主要有兩種方式:

  1. 在 IntelliJ IDEA 中執行;
  2. 執行獨立的命令列。

直接在 IntelliJ IDEA 中執行 REPL

較新版本的 IntelliJ IDEA(以下簡稱 IDEA)中已經內建了 Kotlin 支援,包括 Kotlin REPL。如果已經安裝了 2017 年版的 IDEA,就可以直接在其中執行 Kotlin REPL。

首先需要在 IDEA 中開啟/建立一個 Kotlin 或者 Java 專案,待專案載入完畢之後,點選如下圖所示的選單:Tools -> Kotlin -> Kotlin REPL:

就會出現 Kotlin REPL 視窗:

在 IDEA 內建的 Kotlin REPL 視窗中鍵入的程式碼,需要按 Ctrl-回車(mac 下為 ⌘↩︎)執行。如果想退出 REPL,點視窗左側的叉號按鈕即可。

IDEA 內建的 REPL 有一些優勢,例如像在程式碼視窗當中一樣擁有語法高亮、智慧提示、程式碼補全等,並且能夠執行專案中的程式碼;但是內建的 REPL 也有一些問題,例如,目前版本在 Windows 下漢字輸出為亂碼等。

執行獨立的 REPL 命令列

某些情況下,我們並不適合使用 IDEA 內建的 Kotlin REPL,比如在遠端 Linux 伺服器上,比如需要在 Windows 下輸出漢字時,再如不需要執行專案相關程式碼並希望少佔資源時。這些情況下都更適合使用獨立的 Kotlin REPL 命令列。

如果本機已安裝較新版本的 IDEA,想要執行 Kotlin REPL 就只需找到它然後執行它即可。它位於 IDEA 所安裝目錄下的 plugins/Kotlin/kotlinc/bin 子目錄中,一般來說如果已安裝 JDK 並已設定好 JAVA_HOME 環境變數,只需將上述子目錄設定為命令搜尋路徑即可通過 kotlinc 命令來執行 Kotlin REPL。對於 Windows 在安裝 JDK 並設定好 JAVA_HOME 之後,可以開啟安裝目錄下的相應子目錄,然後直接雙擊 kotlinc.bat 來執行 REPL。

注:如果 IDEA 有更新過小版本(比如 2017.1.3)或者單獨升級過 Kotlin 外掛,那麼較新版本的 Kotlin REPL 有可能不是在 IDEA 的安裝目錄的子目錄下,而是在類似 ~/.IdeaIC2017.1/config/plugins/Kotlin/kotlinc/bin 這樣的目錄中(Windows 下在 %USERPROFILE%\.IdeaIC2017.1\config\plugins\Kotlin\kotlinc\bin 目錄中)。

如果本機沒有安裝 IDEA 或者在遠端 Linux 伺服器上,還可以安裝獨立的 Kotlin 編譯器。開啟 Kotlin 最新穩定版在 GitHub 上的釋出頁(https://github.com/JetBrains/kotlin/releases/latest)。下載其中的 kotlin-compiler-*.zip 檔案,將其解壓到指定的目錄,然後可以將其中 bin 所在路徑加入到系統的搜尋路徑中。當然這也要求提前安裝好 JDK 並配置好 JAVA_HOME。然後就可以在命令列通過 kotlinc 命令來執行 Kotlin REPL 了(Windows 下還是可以找到對應的 kotlinc.bat 雙擊執行)。

獨立執行的 REPL 命令列遵循通用的命令列操作,如 Ctrl-D 退出、Ctrl-R 反向搜尋、Ctrl-S 正向搜尋等等。

如何在 Kotlin REPL 中檢視推斷出的型別

昨天看了 Benny 新發的文章《val b = a?: 0,a 是 Double 型別,那 b 是什麼型別?》,文中詳述了相關現象並分析了原因,是篇深度好文,在此也推薦給大家。

當時就想邊看邊在 REPL 中做實驗,畢竟做實驗這種事情最適合 REPL 做不過了。

遺憾的是 Kotlin REPL 並不回顯型別。在 Kotlin REPL 中鍵入 Benny 文中的示例程式碼:

kotlin
var a: Double? = null
val b = a ?: 0

並不會有任何回顯,如果想看 b 的型別,確實可以這樣來做:

>>> b::class
class kotlin.Int

這回看到的是 Int,但是這是有問題的。 通過 b::class 這種方式得到的是 b 實際求值結果 0 的型別,而不是 Kotlin 針對 a ?: 0 這個表示式,在實際求值之前(編譯階段)為 b 推斷出的型別。 如果想看 Kotlin 求值之前推斷出的型別,該怎麼辦呢?

答案是用 lambda 表示式,實際上我在上篇文章《Kotlin 版圖解 Functor、Applicative 與 Monad》 中有提及過,只是不明顯:

> {y: Int -> {x: Int -> x + y}} `($)` Maybe.Just(5)
Just(value=(kotlin.Int) -> kotlin.Int)

value= 後面的部分就是 Kotlin 對一個 lambda 表示式的輸出形式,我們可以看一個更直觀的例子:

>>> val f = { 1 }
>>> f
() -> kotlin.Int

f 是一個無參且返回值為 1 的 lambda 表示式。當在 REPL 中對 f 求值時,REPL 中輸出了該 lambda 表示式的型別。這個例子還可以進一步簡化為:

>>> {1}
() -> kotlin.Int

這樣通過 lambda 表示式的返回值型別就能看出 1 在 Kotlin 中被推斷為 Int。如果要在 REPL 中看 Benny 文章標題所說的示例,只需這樣就可以了:

>>> {
... val a: Double? = 2.0
... a ?: 0
... }
() -> kotlin.Any

通過 lambda 返回值的型別可以看出,a ?: 0 會被推斷為 Any。這裡 a 的值是 null 還是 2.0 並不影響型別推斷的結果。 我們還可以再看幾個例子來印證 Benny 的文章:

>>> {if (true) 1 else 2.0}
() -> kotlin.Any
>>> interface I; class A : I; class B : I;
>>> {if (false) A() else B()}
() -> Line_8.I

Line_8.I 表示第 8 行定義的型別 I,也就是說,對於 IntDouble 推斷出的公共基類是 Any,而對於 AB 推斷出的公共基類是 I,完全印證了 Benny 文中所講的內容。

本文也發在我的個人部落格上:https://hltj.me/kotlin/2017/08/31/2tips-for-kotlin-repl.html

相關文章