owned this note
owned this note
Published
Linked with GitHub
---
title: 'Kotlni Infix 中綴表達 & Operator & Kotlin DSL'
disqus: kyleAlien
---
Kotlni Infix 中綴表達 & Operator & Kotlin DSL
===
## Overview of Content
以下參考,Kotlin 進階實戰
如有引用參考本文章請詳註出處,感謝 :smile:
:::success
* 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/)
本篇文章對應的是 [**Kotlin DSL、操作符、中綴表達式 Infix | DSL 詳解 | DSL 設計與應用**](https://devtechascendancy.com/kotlin-dsl-operators-infix-explained/)
:::
[TOC]
## 運算符重載
Java 中無法運算符重載,但 Kotlin 如同 C++ 一樣可以運算符重載,不過 **Kotlin 的運算符是指定函數名**(不是真正的符號)
> 以下會舉幾個操作符重載的例子、使用,更多的符號 Mapping 函數名,請參考官方 [**Kotlin 操作符重載**](https://book.kotlincn.net/text/operator-overloading.html) 說明
### operator 重載範例:一元、二元操作符
* Kotlin 透過 `operator` 關鍵字來做到操作符重載,`operator` 修飾 **特定函數名** 的函數
1. **一元操作符**
| 表達式 | Kotlin 重寫 |
| - | - |
| +a | a.unaryPlus() |
| +a | a.unaryMinus() |
| !a | a.not() |
| a++ | a.inc() |
| a-- | a.dec() |
2. **二元操作符**
| 表達式 | Kotlin 重寫 |
| - | - |
| a + b | a.plus(b) |
| a - b | a.minus(b) |
| a * b | a.times(b) |
| a / b | a.div(b) |
| a % b | a.rem(b)、 a.mod(b) (已棄用) |
| a..b | a.rangeTo(b) |
:::success
* 完整的操作符請看 [**Kotlin 官方介紹**](https://www.kotlincn.net/docs/reference/operator-overloading.html)
:::
* 操作符複寫範例:覆寫 `plus`(對應 `+` 號), `contains`(對應 `in` 號) 操作符
```kotlin=
class Balance(var money : Int) {
// 加法
operator fun plus(balance: Balance) : Balance {
println("money: ${this.money}, other balance: ${balance.money}")
return Balance(balance.money + this.money) // 可創建新對象返回
}
// 操作函數也可以重載
operator fun plus(value : Int) : Balance {
println("money: ${this.money}, value: $value")
this.money += value
println("this money: ${this.money}")
return this // 可直接返回 this
}
operator fun contains(balance: Balance) : Boolean {
println("contains money: ${this.money}, value: ${balance.money}")
return this.money == balance.money
}
}
operator fun String.times(n : Int) : String {
val builder = StringBuffer()
repeat(n) {
builder.append(this)
}
return builder.toString()
}
fun main() {
val p1 = Balance(100)
val p2 = Balance(30)
val p3 = Balance(30)
val p4 = p1 + p2 + p3
println("res = ${p4.money}")
println("\nres = ${(p4 + 100).money}")
if(p2 in p3) {
println("\np2 in p3")
} else {
println("\np2 not in p3")
}
// val a : Long = 100L
// println("res = ${(p4 + a).money}") // Int 不會自動匹配 Long (C++ 可以)
val t = "123" * 3
println("\nTest: $t")
}
```
:::warning
* 上面註解中有說到 Int 不會自動匹配 Long 類型,這同時也說明了 Kotlin 是一門「強類型」語言
:::
> 
## 中綴表達式 infix
操作符以中綴(`infix`)形式處於兩個待操作數中間!通常可以用在更人性化的表達方式(想想… 就跟你在寫 SQL 時一樣)
:::success
中綴表達式可以表達成類似文本閱讀的效果,它可以省略一般在寫程式時的 `.`、`()` 符號
:::
* 目前在 Kotlin 中要使用中綴表達式要符合幾個條件,條件如下…
1. 函數需使用 `infix` 關鍵字
2. 只能接收 **一個參數**
3. 不接受 `vararg` 參數
4. 參數不可有默認值
5. 必須定義在「類的方法」中,拓展函數也可以(不能定義在頂層方法)
### infix 使用範例
* 依照 Kotlin 對中綴函數的定義條件寫以下範例
```kotlin=
// 定義在 Collection (接口) 中
infix fun <T> Collection<T>.has(element: T) = contains(element)
// 定義在 A 類中
infix fun <A, B> A.with(that: B): Pair<A, B> = Pair(this, that)
// 不可定義在頂層函數!
/*infix*/ fun <T> test(value : T) = println("$value")
// 只可以定義一個參數
infix fun <T> String.test(value : T/*, i : Int*/) = println("$this: $value")
fun main() {
val list = listOf(1, 2, 3, 4, 5)
if (list has 1) { // 可省略 `.`、`()` 符號,更像英文的表達
println("Container 1")
} else {
println("Not container 1")
}
val map = mapOf<Int, String>(1 to "A", 2 to "B", 3 to "C")
println("map[1]: ${map[1]}")
val map2 = mapOf<Int, String>(1 with "A", 2 with "B", 3 with "C")
println("map2[2]: ${map2[2]}")
"123".test("Hello")
}
```
> 
## DSL 概述
`Domain-Specific Language`, DSL 是指 **特定領域語言**,是為了要簡化程序並方便理解,讓非該領域的人員也可以描述的語言
在 Android 中最常見的 DSL 語言就是編寫 Gradle 用的 `Groovy`
### DSL 分類:內部、外部 DSL
* **DSL 一般分為兩種**
1. **外部 DSL**:不同於應用系統,**它是描述語言的語言**,**通常有「特殊符號」、「格式」**(也就是特殊文本);應用程式通常會透過「符號」與「格式」來解析外部 DSL 語言,在轉為城市內部可用的語言!
> eg. 正則表達式,SQL,AWK... 等等
2. **內部 DSL**:通用語言的 **特定語法**,**它是合法程式**(自身就屬於程式);但它具有特定風格,專注處理小領域的問題~
:::success
* 內部 DSL 特別注重「上下文的概念」,每個 DSL 都會帶入不同的上下文!
**「上下文」可理解為「環境」**
:::
:::info
* 接下來討論的都是 **內部 DSL**,它通過 Kotlin 來達到 DSL 效果!我們將會討論的、實現的案例如下
* **帶接收者的 Lambda**
* **運算符重載**
* **中綴表達式**
:::
### 帶接收者的 Lambda:研究 Kotlin 語法糖 `with`、`apply`
* 什麼是帶接收者的 Lambda?
帶接收者的意思就是 **函數內部帶有拓展類的 `this` 對象,可以直接調用該對象內的成員**;也就是說這種 DSL 會帶有「**接收者的上下文環境**」!
```shell=
## A 是接收者類型
## B 是參數類型
## C 是返回類型
A.(B)->C
```
以下範例,使用拓展函數創建一個 Int 類型的 DSL,也就是說該 **DSL 的上下文環境就是 Int**!(`this` 代表了 Int)
```kotlin=
fun main() {
// Normal lambda
val sum1 : (Int, Int) -> Int = {
x : Int, y : Int -> x + y
}
// Int DSL
val sum2 : Int.(Int) -> Int = {
// this 是當前的數值!
// it 是傳入的數值!
this + it
}
println(sum1.invoke(1, 2))
println(1.sum2(2))
}
```
上面 DSL 的寫法 `this` 代表了 1,`it` 代表了 2,最終合計就是 3
> 
* **Kotlin 自帶的接收者 Lambda 語法糖**
| 函數 | 特色 |
| - | - |
| `with` | 帶新返回值的接收者 Lambda |
| `apply` | 無返回接收者 Lambda |
1. **Kotlin 語法糖:`with` 原型**
`with` 會將第一個參數作為上下文,當作第二個參數的上下文!
```kotlin=
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
```
範例:`with` 使用範例如下,將第一個參數設定為 `AccountInfo`,接著第二個 Lambda 參數就會用 `AccountInfo` 作為上下文
```kotlin=
class AccountInfo {
var name : String? = null
var address : String? = null
override fun toString(): String =
"name: $name, address: $address"
}
fun main() {
val msg = with(AccountInfo()) {
name = "Alien"
address = "Earth-Taiwan"
toString() // 返回 String
}
println(msg)
}
```
2. **Kotlin 語法糖:`apply` 原型**
`apply` 是配合泛型、拓展函數、Lambda 做出的(我們先專注在 DSL 的部分),DSL 會帶入泛型 `<T>` 的上下文,也就是 **將呼叫者作為上下文帶入 Lambda 中**!
```kotlin=
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
```
範例:`apply` 使用範例如下,將 `AccountInfo` 作為接收者,帶入 Lambda 作為上下文
```kotlin=
class AccountInfo {
var name : String? = null
var address : String? = null
override fun toString(): String =
"name: $name, address: $address"
}
fun main() {
val info = AccountInfo().apply {
name = "Alien"
address = "Earth-Taiwan"
}
println(info)
}
```
### 帶接收者的 Lambda:自訂 DSL
1. **基礎類**(上下文 Class 類)
之後會透過 DSL 帶入這兩個類的上下文,讓使用者來設定其內部的成員屬性!
```kotlin=
class ClassInfo {
// 成員屬性
var className: String? = null
var studentCount: Int = 0
var teacherInfo: TeacherInfo? = null
override fun toString(): String {
return "Class name: $className, studentCount: $studentCount\n$teacherInfo"
}
}
class TeacherInfo {
// 成員屬性
var name: String? = null
var age: Int? = null
override fun toString(): String = "Teacher Name: $name, age: $age"
}
```
2. **ClassWrapper 類**:
該類的職責是包裝 `TeacherInfo`、`ClassInfo` 類… 目的是 ^1.^ **對外提供給 `TeacherInfo` 類物件的實例**,之後提供給呼叫者來設定 `TeacherInfo` 的內部成員;^2.^ 提供與 `ClassInfo` 相同的成員,目的是不讓使用者直接設定 `ClassInfo` 類的成員
```kotlin=
// 對外提供(暴露)
class ClassWrapper {
// 建構一個預設物件,這個物件會提供給外部使用者
private val teacherInfo = TeacherInfo()
// 提供與 `ClassInfo` 相同的成員
var className: String? = null
var studentCount: Int = 0
// 匿名 Lambda
fun teacherInfoSetup(init: TeacherInfo.() -> Unit) : TeacherInfo {
teacherInfo.init()
return teacherInfo
}
internal fun getTeacherInfo() = teacherInfo
}
```
:::warning
* **看不懂 `teacherInfoSetup` 函數中的 `teacherInfo.init()`**?
其實 `init` 參數就是接收一個 `TeacherInfo` 物件的匿名拓展 Lambda 函數,我們也可以將其寫的更清晰好懂一點(如下)
```kotlin=
fun teacherInfoSetup(init: TeacherInfo.() -> Unit) : TeacherInfo {
// teacherInfo.init()
init.invoke(teacherInfo) // 同上,一樣的效果
return teacherInfo
}
```
:::
3. **頂層函數 `DslWithClassInfo`**:透過頂層函數,配合 Kotlin 的擴展函數寫法,就可以完成 DSL 的設置
這個函數的目的就是 **替使用者創建各種上下文環境(像是 `ClassWrapper`、`ClassInfo`)**
```kotlin=
// DSL 使用 Wrapper
fun DslWithClassInfo(init: ClassWrapper.() -> Unit) : ClassInfo {
val wrapper = ClassWrapper()
// 創建出 ClassWrapper 後,就可以執行拓展函數 `init`(如果不清楚,請看上一個小姐的說明)
wrapper.init()
val classInfo = ClassInfo()
classInfo.className = wrapper.className
classInfo.studentCount = wrapper.studentCount
classInfo.teacherInfo = wrapper.getTeacherInfo()
return classInfo
}
```
* 使用:最終達到 DSL 效果(如同 Android Gradle 使用);如果好好利用這個特性的話可以加強「物理高內聚」的特性!
```kotlin=
fun main() {
val classInfo = DslWithClassInfo { // 上下文帶入的 this 是 ClassWrapper
className = "Apple"
studentCount = 20
// 物理高內聚
teacherInfoSetup { // 上下文帶入的 this 是 TeacherInfo
name = "Alien"
age = 2000
}
}
println(classInfo)
}
```
> 
### DSL 配合操作函數
* 要創造 DSL 相似的效果,就要覆寫相對應的操作符,一般是寫 `invoke` 函數,再加上 Lambda 拓展函數… 範例如下
> Kotlin 的符號有對應的函數名稱,像是:`invoke` 代表了 `()` 操作符
1. 頂層函數 + Lambda 拓展函數,來創建 String DSL 環境
```kotlin=
operator fun String.invoke(fn: String.() -> Unit) {
fn(this)
}
fun main() {
"Hello Dsl" {
println(this)
}
}
```
> 
2. 頂層函數 + Lambda 拓展函數,來創建 指定類的 DSL 環境
```kotlin=
class Dependency {
fun implementation(lib: String) {
println("lib: $lib")
}
operator fun invoke(action: Dependency.() -> Unit) {
action()
}
}
fun main() {
val dependency = Dependency()
dependency() {
implementation("Test_1111")
implementation("Test_2222")
implementation("Test_3333")
}
}
```
> 
### DSL配合:中綴表達式
* Kotlin 使用 **`infix` 關鍵字** 後就可以使用中綴表達式;以下透過透過中綴表達式加上拓展函數,來創建 DSL 的效果,讓趨近於自然語言的表達方式!
```kotlin=
infix fun Int.isBigThan(value : Int) : Boolean {
return this > value
}
infix fun Int.isSmallThan(value : Int) : Boolean {
return this < value
}
fun main() {
println("1 isBigThan 2: ${1 isBigThan 2}")
println("1 isSmallThan 2: ${1 isSmallThan 2}")
}
```
> 
## 更多的 Kotlin 語言相關文章
在這裡,我們提供了一系列豐富且深入的 Kotlin 語言相關文章,涵蓋了從基礎到進階的各個方面。讓我們一起來探索這些精彩內容!
### Kotlin 語言基礎
* **Kotlin 語言基礎**:想要建立堅實的 Kotlin 基礎?以下這些文章將帶你深入探索 Kotlin 的關鍵基礎和概念,幫你打造更堅固的 Kotlin 語言基礎
:::info
* [**Kotlin 函數、類、屬性 | DataClass、Sealed、Object 關鍵字 | Enum、Companion、NPE**](https://devtechascendancy.com/kotlin-functions_oop_dataclass_sealed_object/)
* [**深入探究 Kotlin 與 Java 泛型:擦除、取得泛型類型、型變、投影 | 協變、逆變**](https://devtechascendancy.com/explore-kotlin-java-generics_type_erasure/)
* [**深入 Kotlin 函數特性:Inline、擴展、標準函數全解析 | 提升程式碼效能與可讀性**](https://devtechascendancy.com/kotlin_inline_extensions_standards-func/)
* [**Kotlin DSL、操作符、中綴表達式 Infix | DSL 詳解 | DSL 設計與應用**](https://devtechascendancy.com/kotlin-dsl-operators-infix-explained/)
:::
### Kotlin 特性、特點
* **Kotlin 特性、特點**:探索 Kotlin 的獨特特性和功能,加深對 Kotlin 語言的理解,並增強對於語言特性的應用
:::warning
* [**Kotlin 代理與懶加載機制:使用、lazy 深度解析**](https://devtechascendancy.com/kotlin-delegate_java-proxy_lateinit_lazy/)
* [**Kotlin Lambda 編程 & Bytecode | Array & Collections 集合 | 集合函數式 API**](https://devtechascendancy.com/kotlin-lambda-bytecode-array-collections-functional/)
* [**深入理解 Kotlin:智能推斷與 Contact 規則**](https://devtechascendancy.com/kotlin-smart-inference-contract-rules-guide/)
:::
### Kotlin 進階:協程、響應式、異步
* **Kotlin 進階:協程、響應式、異步**:若想深入學習 Kotlin 的進階主題,包括協程應用、Channel 使用、以及 Flow 的探索,請查看以下文章
:::danger
* [**應用 Kotlin 協程:對比 Thread、創建協程、任務掛起 | Dispatcher、CoroutineContext、CoroutineScope**](https://devtechascendancy.com/applied-kotlin-coroutines-in-depth-guide/)
* [**Kotlin Channel 使用介紹 | Select、Actor | 生產者消費者**](https://devtechascendancy.com/kotlin-channel_select_actor_cs/)
* [**探索 Kotlin Flow:基本使用、RxJava 對比、背壓機制 | Flow 細節**](https://devtechascendancy.com/kotlin-flow-usage_compare-rx_backpressure/)
:::
## Appendix & FAQ
:::info
:::
###### tags: `Kotlin`