今天我們嘗試換個語言,用 kotlin 來解決非同步版的樹狀搜尋問題,並觀察語言之間的差異
data class Node(
val id: String,
val text: String,
val childrenIds: List<String>,
)
class NodeNotFoundError(
val id: String,
) : Exception()
suspend fun getNode(id: String): Node =
when (id) {
"a" -> Node("a", "a", childrenIds = listOf("a1", "a2", "a33"))
"a1" -> Node("a1", "a1", childrenIds = listOf("a11"))
"a11" -> Node("a11", "a11", childrenIds = listOf())
"a2" -> Node("a2", "a2", childrenIds = listOf())
"a3" -> Node("a3", "a3", childrenIds = listOf())
else -> throw NodeNotFoundError(id)
}
suspend fun getNameById(
id: String,
target: String,
): String? =
runCatching { getNode(id) } // 把錯誤包裹成 Result Class
.fold( // 分別處理錯誤和成功,並整合成一種型別 String? (nullable String)
onSuccess = { node ->
if (node.id == target) {
node.text // 找到,回傳
} else {
// 沒找到,往下一層找
node.childrenIds.map { getNameById(it, target) }.find { it == target }
}
},
onFailure = {
// 有某個 id 找不到,進行錯誤處理
println("get id $id failed, skip this node")
it.printStackTrace()
null
},
)
對照 Typescript 的寫法如下
Typescript | Kotlin | 解決的問題 | 差異 |
---|---|---|---|
async/await | suspend | 把非同步寫作風格簡化 | kotlin 相對更簡單 |
Union Type (E|T) | Result<T> | 更加安全的進行錯誤處理 | kotlin 缺少錯誤型別 |
Promise.all | 無 | 更有效率的非同步流程控制 | 原生 kotlin 缺少有效的非同步控制工具 |
early return | when expression | 簡化程式碼複雜度 | - |
要解決以上方法其實也非常簡單,只需要安裝 Arrow-KT
即可
<dependency>
<groupId>io.arrow-kt</groupId>
<artifactId>arrow-core</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>io.arrow-kt</groupId>
<artifactId>arrow-fx-coroutines</artifactId>
<version>1.2.4</version>
</dependency>
有了新工具,我們再根據新工具改寫一下
//不拋出錯誤,而是用回傳型別包裹錯誤
suspend fun getNodeV2(id: String): Either<NodeNotFoundError, Node> =
when (id) {
"a" -> Node("a", "a", childrenIds = listOf("a1", "a2", "a33")).right()
"a1" -> Node("a1", "a1", childrenIds = listOf("a11")).right()
"a11" -> Node("a11", "a11", childrenIds = listOf()).right()
"a2" -> Node("a2", "a2", childrenIds = listOf()).right()
"a3" -> Node("a3", "a3", childrenIds = listOf()).right()
else -> NodeNotFoundError(id).left()
}
suspend fun getNameByIdV2(
id: String,
target: String,
): String? =
getNodeV2(id)
.fold(
ifRight = { result ->
if (result.id == target) {
result.text
} else {
// parMap 相當於平行地執行 map,解決效能問題
result.childrenIds
.parMap { getNameByIdV2(it, target) }
.find { it == target }
}
},
ifLeft = {
// 錯誤處理時也可以具有型別安全性
println("get id ${it.id} failed, skip this node")
it.printStackTrace()
null
},
)
最後我們會得到一個新的表格如下
Typescript | Kotlin | 解決的問題 | 差異 |
---|---|---|---|
async/await | suspend | 把非同步寫作風格簡化 | kotlin 相對更簡單 |
Union Type (E|T) | Either<E|T> | 更加安全的進行錯誤處理 | - |
Promise.all | parMap | 更有效率的非同步流程控制 | - |
early return | when expression | 簡化程式碼複雜度 | - |