2024年10月16日 星期三

Kotlin函數式程式設計

 文/張益裕 Michael Chang

Kotlin支援高階函式(higher-order function),這表示函式可以宣告為一個變數,所以函式可以宣告接收函式的參數,也可以傳回一個函式。Kotlin同時支援物件導向與函數式技術,使用物件導向的特性,設計複雜與容易擴充的應用程式架構,使用高階函式的特性,可以大幅度簡化程式設計。

在Kotlin程式設計語言,除了各種一般物件型態的變數,您還可以宣告型態為「函式」的變數。跟宣告一般的函式相同,您必須定義函式的參數與回傳值,再使用lambda提供這個函式的實作。


// 接收兩個Int參數並回傳參數合計
val summary01: (Int, Int) -> Int = { x, y -> x + y }
// 可以省略型態的宣告
val summary02 = { x: Int, y: Int -> x + y }

// 沒有參數、沒有回傳值
val hello01: () -> Unit = { println("Hello Kotlin!") }
// 可以省略型態的宣告
val hello02 = { println("Hello Kotlin!") }


println("\n===== summary01 & summary02")
println( "summary01(3, 5): ${ summary01(3, 5) }" )
println( "summary02(3, 5): ${ summary02(3, 5) }" )

println("\n===== hello01 & hello02")
hello01()


在定義Kotlin函式的時候,除了接收一般物件型態參數,也可以接收函式型態參數。根據函式的實作,函式型態參數可以使用lambda expression設定預設值,或是允許接收null參數。

// response參數為函式型態,包含兩個參數,沒有回傳值
fun request(url: String,
             response: (code: Int, reason: String) -> Unit ){
    println("request $url...")
    val code = 200
    val reason = "OK"
    response(code, reason)
}

fun main(args: Array<String>) {
    request("www.codedata.com.tw") {
        code, reason -> println("$code: $reason")
    }
}

一般的函式可以根據需求定義一個回傳值型態,回傳值可以是任何物件或基本資料型態。高階函式允許函式的回傳值為函式型態,這樣的作法可以讓應用程式的設計更加靈活。

// 客戶等級與對應的折扣
enum class Level(val discount: Double) {
    NORMAL(0.9), VIP(0.8), GOLDEN(0.7)
}

// 訂單類別,包含訂單數量
data class Order(var amount: Int)

// 計算折扣的函式,接收訂單物件參數
// 回傳計算折扣的函式,參數為訂單物件,回傳值為Double,依照訂單數量計算實際的折扣
fun getDiscount(level: Level): (Order) -> Double =
        { order ->
            val amountDiscount =
                when (order.amount) {
                    in 1..10 -> 0.1
                    in 11..20 -> 0.2
                    in 21..30 -> 0.3
                    else -> 0.4
                }
            // 在lambda expression裡面可以使用外層函式的參數level
            level.discount - amountDiscount
        }

使用函式的特性,可以簡化處理複雜折扣計算的應用:

// 取得計算折扣的函式變數
val discount = getDiscount(Level.NORMAL)
println( discount( Order(16) ) )

val discount2 = getDiscount(Level.VIP)
println( discount( Order(3) ) )

加入回傳函式的特性後,可以比物件導向設計更加靈活,提供另外一個的範例:

// 會員類別,包含名稱與編號
data class Member(val firstName: String,
                  val lastName: String,
                  val id: String)

// 會員名稱種類
enum class MemberName {FIRST, LAST, ALL}

// 會員名稱搜尋函式,接收搜尋內容與種類參數
// 回傳會員過濾條件函式,參數為會員物件,回傳值為Boolean,依照名稱種類執行判斷
fun getMemberPredicate(prefix: String,
                       pn: MemberName): (Member) -> Boolean =
    when (pn) {
        MemberName.FIRST ->
            { p: Member -> p.firstName.startsWith(prefix) }
        MemberName.LAST ->
            { p: Member -> p.lastName.startsWith(prefix) }
        MemberName.ALL ->
            { p: Member -> p.firstName.startsWith(prefix) ||
                    p.lastName.startsWith(prefix)}
    }

fun main(args: Array<String>) {
    val people = listOf(Member("Simon", "Johnson", "101"),
                        Member("Mary", "Johnson", "102"),
                        Member("Sam", "Johnson", "101"))

    println("\n===== First name starts with Si")
    val predicate01 = getMemberPredicate("Si", MemberName.FIRST)
    people.filter(predicate01).forEach { println(it) }

    println("\n===== First name or last name starts with Jo")
    val predicate02 = getMemberPredicate("Jo", MemberName.ALL)
    people.filter(predicate02).forEach { println(it) }
}



相關學習資源

0 意見:

張貼留言