LINE Corporation 於2023年10月1日成爲 LY Corporation。LY Corporation 的新部落格在這裏。LY Corporation Tech Blog

Blog


Kotlin DSL: A smart way to describe

Introduction

大家好,我是 Android team 的 TECH FRESH TU。

作為一位 Android 開發者,我們時常在開發應用程式的過程中遇到各種挑戰和需求。在這個不斷變化的技術世界中,我們不斷尋找新的解決方案和工具來提高我們的效率和程式碼品質。在這樣的背景下,我注意到 Gradle 不僅可以使用傳統的 Groovy 語言,還可以使用 Kotlin 這種現代化的語言來編寫。這都要歸功於 Kotlin DSL (Domain-Specific Language) 的應用。

回想起去年在台大醫院國際會議中心參加的 JCConf,我深受 Recca Chao 大大一個議程的啟發。當時他在演講中提到:「投影片還沒做好?沒問題,就用 Kotlin 寫一個吧!」這段經驗深深地激發了我對 Kotlin DSL 的興趣。

在議程中,Recca Chao 介紹了 kslides 這個開源專案,它是一個使用以 Kotlin 為基底的 DSL (Domain-Specific Language),來建立精美的演講投影片的工具。kslides 基於 reveal.js 框架開發,它可以讓我們在熟悉的 IDE or Editor 中創建投影片,並且特別適合製作包含程式碼片段和 HTML 動畫的頁面。我們可以在 kslides 中使用 Markdown、HTML、Kotlin HTML DSL 等方式來進行製作。

以下我將分享我從 JCConf 2022 中學到的關於 Kotlin DSL 的知識和經驗。我將提供一些簡易的範例,讓大家更好地理解 Kotlin DSL 的實現原理。

首先,以下為 Kotlin DSL 的一個簡單範例:

這裡用一個名為「Lakeside Farm」的農場來舉例,該農場種植葡萄,且位於座標 (124, 56)。

從上述的範例我們可以發現,我們的意圖可以清楚地透過 DSL 表達出來。

那麼 Kotlin 語言是透過了什麼方式實現這種寫法的呢?答案是:Trailing lambdas  Function literals with receiver

Trailing lambdas

如果某個 function sleep 的最後一個 parameter dream  Function type,那麼當呼叫 sleep 時,可以將作為 dream  lambda function 放在括號外傳遞。

Function literals with receiver

在一個基本的 Function type 的括號左邊加上某個 class name 後,這個 type 就成為了具備 receiver 的 Function type。

具備 receiver type 的 function 最主要是能夠讓我們在它的函式主體中,以 this 的方式使用到 receiver instance,而在 Kotlin 中,使用到 this 中的成員變數、函式時,可以將 this 省略不寫。

例如:

DSL 從零到有

看過了上述的簡介後,讓我們來分析一下最初的 farm DSL 是怎麼製作的吧。

首先,我們最初的 class 長這樣:

可以看到我們製作了一個最基本的 Farm class (注意這裡的 Farm F 是大寫),以及一個 showFarm function 來輸出 Farm instance, "myFarm" 的各個 property.

現在的需求是,我們要在 showFarm 中設定 myFarm 的各項屬性值。最常見的做法就是直接設定 Farm class 中的各項屬性值為 mutable variable (var), 然後在 myFarm init 後依序進行設定。

或許看到這裡你會覺得:為什麼不使用 data class ? 這是因為接下來我們就要來製作 DSL class, 所以這邊暫不考慮這項作法。

下一步,我們開始調整 name 屬性的設定過程,把 name 這東西變成了一個 function, 並且加了一個名為 nameBlock 的 function-type property 到 Farm class 中。

這邊的概念是,在 toString 方法中改為執行 nameBlock 來得到真正的 name 以作為輸出,換句話說,原本 Farm instance 中直接儲存 name 的值,現在改為儲存一個「產生出 name 的 function」。

有了這項變動後,我們就可以再結合 Trailing lambdas 的概念,讓 name 的值透過大括號傳給 myFarm。

接下來讓我們大幅推進一下。

在這個階段中,我們可以看到 showFarm 中的內容已經全數換成 DSL 的 syntax,這之間發生了什麼?

首先,按照上一步的作法,我們把除了 name 以外的另兩個 property 都套用 Trailing lambdas 的概念,儲存個別的「產生值的 function」。

接下來,為了要能夠在最外層用 DSL 的方式初始化 Farm 的 instance, 我們新增了一個 farm function (f 小寫)。在這 function 中我們可以看到,它接受的參數型態是一個具備 receiver 的 Function

複習上面提到的 Function literals with receiver 概念,在這樣子型態的 function 中,this 就等於一個 receiver class instance,因此我們可以直接使用 this 底下的各種 methods & properties 而無需寫 this

所以在 farm 中,我們就可以直接呼叫 name, location, crops 這三個 function。

接下來,我們希望 location 的座標值可以直接用兩個獨立的變數,x & y 來設定,而非是透過字串。要達到此目標,我們可以先製作一個 Location class。(不一定要 data class)

然後將 Farm class 中的 locationBlock 改為一個以 Location 為 receiver 的 Function type

此外,我們需要建立一個 location 的實體,用以儲存 x 與 y 的值。

在這幾步驟中,我們希望透過 location function 來設定 locationBlock,並預期執行 locationBlock 時,location instance 中的 x 和 y 會被修改為我們想要的座標,當最後印出結果時,再呼叫 location instance toString() 就行。

在這邊我們需要再提到一個概念,就是當呼叫一個具有 receiver 的 function 時,該 function 的第一個參數必須是 receiver。

這就代表,當上圖中的 locationBlock 被呼叫時,需要放入一個真正的 location instance 作為第一個參數。

綜上所述,我們為了需要修改 Farm 中的 location instance 的 x & y, 會需要呼叫 locationBlock, 並帶入 location instance 作為 receiver function 的第一個參數,因此 farm function 需要加入上圖中新增的 locationBlock 呼叫。

最後,我們就可以透過指定 x & y 的方式來寫 DSL 啦~

結尾附上最終的完整程式碼:

如果你對 DSL 還有興趣的話,這邊可以接這看看 DslMarker 這個 annotation, 它能夠避免非預期的 DSL 嵌套發生。詳細請看官方說明

總結

Kotlin DSL 的使用可以提高程式碼的可讀性和可維護性,並簡化常見的操作流程。它可以用於許多領域,包括網頁應用程式、測試框架、建構工具、配置文件 (ex. gradle)等。

要了解 Kotlin DSL 的原理,你需要先熟悉 Kotlin 語言的一些基本知識,包括類型系統、函數、擴展函數、委託和其他功能。你還需要了解關於 DSL 的一些基本概念,包括類型安全、延遲執行、delegate 和其他技術。

具體來說,Kotlin DSL 可以通過以下幾種方式實現:

- 使用類型安全的 extension function 和 extension property 來實現簡潔的語法。

- 使用 delegate 來實現延遲執行。(ex: lazy)

- 使用類似於 builder pattern 的方法來構建物件。

在參加 JCConf Taiwan 的這段日子裡,我還有機會參觀了其他議程,並與其他開發者交流。我覺得這是一個非常有意義的活動,讓我能夠學習到新知識,並和社群中的其他成員分享想法。我希望有機會能再參加這類活動,並繼續學習新的知識和技能。

在這邊想特別感謝 LINE 提供企業贊助票讓我可以參加 JCConf 這個 Java 年度盛會。當時除了聽了很多有深度的議程,還發現早午餐時間在攤位上也有許多令人耳目一新的 sharing。

總結來說,研究 Kotlin DSL 讓我深刻的體會到了 Java & Kotlin 對現今科技發展的深遠影響。對 Kotlin or Java 有興趣的你不妨自己動手試試,或參加未來的 JCCOnf 來聽大大們分享神奇的技術知識~