眾所皆知 Kotlin 是一個需要編譯的程式語言,所以每次一更動程式就必須重新編譯一次。雖然編譯式的語言對程式正確性和效能都有不錯的幫助,但假如你是拿 Kotlin 做大量的資料處理時,這樣的特性讓開發效率無法與其他直譯語言相比。有沒有什麼辦法可以讓 Kotlin 有類似直譯語言的敏捷,同時又有編譯語言的安全與效能呢?
我們很幸運,Kotlin 還真的可以!
Kotlin 雖然是編譯式語言,每次運行在 JVM 前都需要先用 Kotlin 編譯器編譯,編譯後再用 java
指令執行:
$ kotlinc [file].kt -include-runtime -d [file].jar
$ java -jar [file].jar
但其實 Kotlin 編譯器有另外一種如同直譯語言運行程式的方式,上面這兩行指令用 Kotlin Script 來執行的話,就只需要一行:
$ kotlinc -script [file].kts
使用 Kotlin Script 有 3 個重點:第一,程式裡不需要 main
,或說 Kotlin Script 會把整個檔案當成 main
來執行;第二,Kotlin Script 的檔案要以 .kts
做為副檔名;第三,使用 kotlinc
編譯時,要加 -script
參數。
kscript
雖然 Kotlin Script 為 Kotlin 帶來了動態運行的特性,但仍不夠完美。比方說,Kotlin 編譯器實際上還是每次都得重新編譯,就算沒有更動程式碼也一樣;對 IDE 不友善;無法載入相依套件;也沒有打包部署需要的 jar 檔的功能。
幸運的是,由 Kotlin 社群的高手設計出 kscript
工具,解決了上述的這些問題,讓 Kotlin Script 更好用!
kscript
要使用 kscript
很簡單,只要用第三天介紹的 SDKMAN,即可一行指令搞定:
$ sdk install kscript
接著,我們就可以把原本用 kotlinc
的指令換成:
$ kscript [file].kts
甚至我們也可以在 Script 檔案的開頭定義執行方式。
#!/usr/bin/env kscript
然後就可以用像是 Bash Script 的方式行執行:
$ chmod +x [file].kts
$ ./[file].kts
有了 kscript
,假如在 Kotlin Script 裡需要使用第三方套件,也可以用 Annotation 的方式載入,而不需要依頼 Gradle 等工具。
@file:DependsOn("[套件名稱]:[套件版本]")
kscript
也會自動幫你 cache 編譯的結果,若是程式碼沒有變更的話就會直接執行。另外,若想部署程式,也可以一行指令打包成 fat-jar。
$ kscript --package [file].kts
kscript
來做資料處理其實 kscript 的作者 Holger Brandl 之所以開發 kscript,是因為他覺得 Kotlin 是一個非常適合拿來做資料處理的程式語言(驚訝吧!居然不是 Python),而且效能還不差。甚至為了讓 kscript
更好用,他也設計了一包 kscript-support
專門拿來處理大量資料。
只要新增一個 .kts
的檔案,先定義以下這幾行:
#!/usr/bin/env kscript
// 載入 kscript-support
@file:DependsOn("com.github.holgerbrandl:kscript-support:1.2.4")
import kscript.text.*
// 從 Terminal 接收要處理的檔案
val lines = resolveArgFile(args)
有了以上的基礎後,就可以傳入要處理的 .csv
資料,kscript
就會把每一行的數據放到 lines
變數。接著我們就可以用 Collection 的操作來處理這些資料,提取出我們想要的結果。
lines.map { it.trim() }.print()
上面這段範例會把每一行資料的空白或 tab 去除後印出,等同於我們用 awk
這樣處理。
awk '{sub(/[ \t]*$/, "");print}' data/some_flights.tsv
我們也可以截取某一段資料、增加或刪除欄,處理像這種表格型 .csv
資料就可以更輕鬆了。
lines.split().select(10, 1, 12).print()
// 選取每一列資料裡的第 10、1、12 欄並印出
// 等同 awk '{print $10, $1, $12}' data/some_flights.tsv
lines.split().map { listOf(it[1], it[2], "F11-"+ it[7]) }.print()
// 增加一個欄位
// 等同 awk '{print $1, $2, "F11-"$7}' some_flights.tsv
lines.split().selectByName(-3).print()
// 刪除一個欄位
// 等同 awk '!($3="")' data/some_flights.tsv
看完這個系列後,你應該會發現,Kotlin Collection 非常的強大,除了有完整的 API 操作外,加上 kscript 提供的工具,拿 Kotlin 做資料科學不是夢。以 Holger Brandl 在 kscript as substitute for awk 裡分享的數據,效能並不輸給其他工具呢!
身為 Kotlin 愛好者的你,別忘了善用手上好用的工具喔!