倉頡程式語言技術指南:巢狀函式、Lambda 表示式、閉包

华为云开发者联盟發表於2024-07-26

本文分享自華為雲社群《【華為鴻蒙開發技術】倉頡程式語言技術指南【巢狀函式、Lambda 表示式、閉包】》,作者:檸檬味擁抱。

倉頡程式語言(Cangjie)是一種面向全場景應用開發的通用程式語言,旨在兼顧開發效率和執行效能,並提供良好的程式設計體驗。本文將深入探討倉頡語言的主要特點和功能,包括其簡明高效的語法、多正規化程式設計支援、型別安全性、記憶體安全性、高效併發、相容性、領域擴充套件能力、UI 開發支援和內建庫功能。

1. 語法簡明高效

倉頡程式語言提供了一系列簡明高效的語法特性,旨在減少冗餘書寫、提升開發效率。以下是一些關鍵特性:

1.1 巢狀函式

倉頡語言允許在函式體內定義巢狀函式,這些函式可以在外部函式內部呼叫,也可以作為返回值返回:

func foo() {
    func nestAdd(a: Int64, b: Int64) {
        a + b + 3
    }

    println(nestAdd(1, 2))  // 輸出:6

    return nestAdd
}

main() {
    let f = foo()
    let x = f(1, 2)
    println("result: ${x}")  // 輸出:result: 6
}

1.2 Lambda 表示式

倉頡語言支援簡潔的 lambda 表示式,使函數語言程式設計變得更加方便:

let f1 = { a: Int64, b: Int64 => a + b }
var display = { => println("Hello") }

func f(a1: (Int64) -> Int64): Int64 {
    a1(1)
}

main() {
    let sum1 = { a: Int64, b: Int64 => a + b }
    let r1 = { a: Int64, b: Int64 => a + b }(1, 2)  // r1 = 3
    let r2 = { => 123 }()  // r2 = 123
    let f2Res = f({ a2 => a2 + 10 })  // 使用 lambda 表示式
}

2. 多正規化程式設計

倉頡程式語言支援函式式、命令式和麵向物件等多正規化程式設計。開發者可以根據需求選擇不同的程式設計正規化:

  • 函數語言程式設計:支援高階函式、代數資料型別、模式匹配等特性。
  • 物件導向程式設計:支援封裝、介面、繼承等特性。
  • 指令式程式設計:支援值型別、全域性函式等特性。

3. 型別安全

倉頡語言是靜態強型別語言,透過編譯時型別檢查來儘早識別程式錯誤,降低執行時風險。編譯器提供了強大的型別推斷能力,減少型別標註工作:

var sum1: (Int64, Int64) -> Int64 = { a, b => a + b }
var sum2: (Int64, Int64) -> Int64 = { a: Int64, b => a + b }

4. 記憶體安全

倉頡語言提供自動記憶體管理,並在執行時進行陣列下標越界檢查、溢位檢查等,以確保記憶體安全:

func safeArrayAccess(arr: [Int64], index: Int64): Int64 {
    if index < arr.length {
        return arr[index]
    } else {
        println("Index out of bounds")
        return -1
    }
}

5. 高效併發

倉頡語言提供使用者態輕量化執行緒(原生協程)以及簡單易用的併發程式設計機制,適用於高效的併發場景:

func concurrentTask() {
    let task = async { 
        // 執行非同步任務
    }
    await task
}

6. 相容語言生態

倉頡語言支援與 C 等主流程式語言的互操作,採用宣告式程式設計正規化以實現對其他語言庫的高效複用和生態相容:

extern func cFunction(arg: Int64) -> Int64

func callCFunction() {
    let result = cFunction(10)
    println(result)
}

7. 領域易擴充套件

倉頡語言支援基於詞法宏的超程式設計能力,允許在編譯時變換程式碼,支援構建內嵌式領域專用語言(EDSL):

macro square(x: Int64) => x * x
func useMacro() {
    let result = square(4)  // 結果為 16
    println(result)
}

8. 助力 UI 開發

倉頡語言的超程式設計和尾隨 lambda 等特性可以用來搭建宣告式 UI 開發框架,提升 UI 開發效率和體驗:

func createUI() {
    let button = Button(text: "Click Me", onClick: { => println("Button clicked") })
    // 建立並顯示 UI 元件
}

9. 內建庫功能豐富

倉頡語言提供了豐富的內建庫,涵蓋資料結構、常用演算法、數學計算、正則匹配、系統互動、檔案操作、網路通訊、資料庫訪問等功能:

import stdlib

func exampleUsage() {
    let numbers = [1, 2, 3, 4, 5]
    let sum = numbers.reduce(0, { a, b => a + b })
    println("Sum: ${sum}")
}

倉頡程式語言憑藉其簡潔高效的語法、多正規化程式設計支援、型別和記憶體安全、高效併發能力以及廣泛的內建庫功能,為開發者提供了一個強大且靈活的程式設計平臺。無論是進行高效的應用開發、構建複雜的 UI 元件,還是進行領域特定的程式設計,倉頡語言都能滿足開發者的需求。

倉頡程式語言的巢狀函式、Lambda 表示式與閉包解析

1. 巢狀函式

在倉頡程式語言中,巢狀函式指的是定義在另一個函式內部的函式。這種函式在外部函式的作用域內是可見的,但其作用域僅限於外部函式。巢狀函式可以訪問外部函式的區域性變數,並且可以作為返回值返回,使得函數語言程式設計更加靈活。

示例:

func foo() {
    func nestAdd(a: Int64, b: Int64) {
        a + b + 3
    }

    println(nestAdd(1, 2))  // 6

    return nestAdd
}

main() {
    let f = foo()
    let x = f(1, 2)
    println("result: ${x}")
}

在上述示例中,nestAdd 是一個定義在 foo 函式內部的巢狀函式。它不僅可以在 foo 內部被呼叫,還可以作為返回值返回,供外部使用。執行結果為:

6
result: 6

2. Lambda 表示式

Lambda 表示式是倉頡程式語言中一種簡潔的函式定義方式,用於定義匿名函式。Lambda 表示式的語法為 { p1: T1, ..., pn: Tn => expressions | declarations }。其中,=> 之前為引數列表,=> 之後為表示式或宣告序列。Lambda 表示式可以賦值給變數,也可以作為函式的實參或返回值使用。

示例:

let f1 = { a: Int64, b: Int64 => a + b }

var display = { => println("Hello") }   // 無引數的 lambda 表示式

// Lambda 表示式的呼叫
let r1 = { a: Int64, b: Int64 => a + b }(1, 2) // r1 = 3
let r2 = { => 123 }()                          // r2 = 123

func f2(lam: () -> Unit) { }
let f2Res = f2{ println("World") } // OK to omit the =>

在上述示例中,f1 是一個接受兩個引數並返回它們和的 Lambda 表示式。display 是一個無引數的 Lambda 表示式。Lambda 表示式可以直接呼叫,也可以賦值給變數再呼叫。

3. 閉包

閉包是指函式或 Lambda 表示式在定義時捕獲了其外部作用域中的變數,即使在外部作用域之外執行,閉包仍然能夠訪問這些捕獲的變數。閉包的變數捕獲遵循一定的規則:

  • 變數捕獲的規則: 捕獲的變數必須在閉包定義時可見,並且在閉包定義時已經完成初始化。
  • 閉包與變數捕獲: 函式或 Lambda 內訪問外部函式的區域性變數稱為變數捕獲。被捕獲的變數的生命週期由閉包決定,這使得閉包可以在其外部作用域被呼叫時,仍然保持對這些變數的訪問。

示例 1:

func returnAddNum(): (Int64) -> Int64 {
    let num: Int64 = 10

    func add(a: Int64) {
        return a + num
    }
    add
}

main() {
    let f = returnAddNum()
    println(f(10))  // 輸出 20
}

在這個示例中,add 函式捕獲了 num 變數。即使 num 的定義作用域已經結束,add 函式仍然能夠訪問 num

示例 2:

func f() {
    let x = 99
    func f1() {
        println(x)
    }
    let f2 = { =>
        println(y)      // 錯誤,y 在閉包定義時未定義
    }
    let y = 88
    f1()          // 列印 99.
    f2()          // 錯誤
}

在這個示例中,f2 嘗試捕獲 y 變數,但 yf2 定義時尚未定義,因此會導致編譯錯誤。

示例 3:

func f() {
    let x: Int64
    func f1() {
        println(x)    // 錯誤,x 尚未初始化
    }
    x = 99
    f1()
}

在此示例中,f1 嘗試訪問尚未初始化的 x,這也是一個編譯錯誤。

示例 4:

class C {
    public var num: Int64 = 0
}

func returnIncrementer(): () -> Unit {
    let c: C = C()

    func incrementer() {
        c.num++
    }

    incrementer
}

main() {
    let f = returnIncrementer()
    f() // c.num 增加 1
}

在這個示例中,incrementer 捕獲了 c 物件,並可以修改 cnum 成員變數。閉包可以正常訪問並修改捕獲的引用型別變數。

倉頡程式語言中的高階函式與函數語言程式設計特性

1. 高階函式

高階函式是指接受其他函式作為引數或返回函式的函式。高階函式能夠提高程式碼的複用性和可讀性。在倉頡程式語言中,你可以建立和使用高階函式以便在不同的上下文中靈活地操作函式。

示例 1:高階函式的基本使用

func applyFunction(x: Int64, func f: (Int64) -> Int64): Int64 {
    return f(x)
}

func square(n: Int64): Int64 {
    return n * n
}

main() {
    let result = applyFunction(4, square)
    println(result)  // 輸出 16
}

在這個示例中,applyFunction 是一個高階函式,它接受一個整數 x 和一個函式 f 作為引數,並將 x 傳遞給 fsquare 函式計算平方值,並作為引數傳遞給 applyFunction

示例 2:函式返回函式

func makeMultiplier(factor: Int64): (Int64) -> Int64 {
    func multiplier(x: Int64) -> Int64 {
        return x * factor
    }
    return multiplier
}

main() {
    let double = makeMultiplier(2)
    let triple = makeMultiplier(3)
    println(double(5))  // 輸出 10
    println(triple(5))  // 輸出 15
}

在這個示例中,makeMultiplier 函式返回了一個新的函式 multiplier,該函式可以將輸入的整數乘以 factor。你可以建立不同的乘法器(如 doubletriple),並應用於不同的輸入值。

2. 函數語言程式設計特性

倉頡程式語言支援函數語言程式設計特性,如不可變資料、函式組合、柯里化等,這些特效能夠使程式碼更加簡潔和模組化。

示例 1:不可變資料

func modifyValue(x: Int64) -> Int64 {
    let y = x + 1
    return y
}

main() {
    let value = 5
    let newValue = modifyValue(value)
    println(value)     // 輸出 5(原值未改變)
    println(newValue) // 輸出 6(新值)
}

在這個示例中,value 變數的值不會被 modifyValue 函式所改變,這體現了不可變資料的特性。原始值保持不變,而函式返回一個新的值。

示例 2:函式組合

func addOne(x: Int64) -> Int64 {
    return x + 1
}

func multiplyByTwo(x: Int64) -> Int64 {
    return x * 2
}

func compose(f: (Int64) -> Int64, g: (Int64) -> Int64): (Int64) -> Int64 {
    return { x: Int64 => f(g(x)) }
}

main() {
    let addThenMultiply = compose(multiplyByTwo, addOne)
    println(addThenMultiply(3))  // 輸出 8((3 + 1) * 2)
}

在這個示例中,compose 函式將兩個函式 fg 組合成一個新的函式,新的函式先應用 g,然後應用 f。透過組合 addOnemultiplyByTwo 函式,我們得到了一個新的函式 addThenMultiply,它先將輸入加一,然後乘以二。

示例 3:柯里化

func add(a: Int64) -> (Int64) -> Int64 {
    func inner(b: Int64) -> Int64 {
        return a + b
    }
    return inner
}

main() {
    let addFive = add(5)
    println(addFive(10))  // 輸出 15
}

在這個示例中,add 函式是一個柯里化函式,它返回一個新的函式 inner。這個新函式可以將一個整數 b 加到 a 上。透過柯里化,我們可以建立特定的加法函式(如 addFive),並用於後續的計算。

3. 函數語言程式設計的最佳實踐

a. 儘量使用不可變資料: 不可變資料可以減少副作用,使程式碼更容易理解和測試。

b. 充分利用高階函式: 高階函式能夠讓你更靈活地操作函式,提高程式碼的複用性和模組化程度。

c. 關注函式組合和柯里化: 函式組合和柯里化能夠幫助你建立更簡潔和靈活的函式,提高程式碼的表達能力。

d. 避免副作用: 儘量減少函式中的副作用,使函式更具純粹性,增強程式碼的可預測性。

透過理解和應用這些函數語言程式設計特性,你可以編寫更高效、靈活和可維護的程式碼。如果你有其他關於倉頡程式語言或函數語言程式設計的問題,請繼續討論!

總結

倉頡程式語言透過巢狀函式、Lambda 表示式和閉包為開發者提供了靈活和強大的程式設計工具。巢狀函式支援函式內部的區域性定義和返回,Lambda 表示式簡化了匿名函式的定義,而閉包則允許函式訪問其定義時的上下文變數。掌握這些功能能夠幫助開發者更好地進行函數語言程式設計,提高程式碼的靈活性和表達能力。

如果你有其他關於倉頡程式語言的問題或想了解更多高階功能,歡迎繼續討論!

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章