仓颉原生应用编程语言教程(第7期)

112 阅读6分钟

其他现代特性及语法糖****

仓颉编程视频→KCKCJY

函数重载****

仓颉允许在同一作用域内定义多个同名函数。编译器根据参数的个数和类型,来决定函数调用实际执行的是哪个函数。例如,下面的绝对值函数,为每种数值类型都提供了对应的实现,但这些实现都具有相同的函数名abs,从而让函数调用更加简单。

func abs(x: Int64): Int64 { ... }func abs(x: Int32): Int32 { ... }func abs(x: Int16): Int16 { ... }...

命名参数****

命名参数是指在调用函数时,提供实参表达式的同时,还需要同时提供对应形参的名字。使用命名参数可以提升程序的可读性,减少参数的顺序依赖性,让程序更加易于扩展和维护。

在仓颉中,函数定义时通过在形参名后添加 ! 来定义命名参数。当形参被定义为命名参数后,调用这个函数时就必须在实参值前指定参数名,如下面的例子所示:

func dateOf(year!: Int, month!: Int, dayOfMonth!: Int) {...} dateOf(year: 2024, month: 6, dayOfMonth: 21)

参数默认值****

仓颉的函数定义中,可以为特定形参提供默认值。函数调用时,如果选择使用该默认值做实参,则可以省略该参数。

这个特性可以减少很多函数重载或者引入建造者模式的需求,降低代码复杂度。

func dateOf(year!: Int64, month!: Int64, dayOfMonth!: Int64, timeZone!: TimeZone = TimeZone.Local) {    ...} dateOf(year: 2024, month: 6, dayOfMonth: 21) // okdateOf(year: 2024, month: 6, dayOfMonth: 21, timeZone: TimeZone.UTC) // ok

 

尾随 lambda(trailing lambda)****

仓颉支持尾随 lambda 语法糖,从而更易于 DSL 中实现特定语法。具体来说,很多语言中都内置提供了如下经典的条件判断或者循环代码块:

if (x > 0) {    x = -x} while (x > 0) {    x--}

 

尾随 lambda 则能够让 DSL 开发者定制出类似的代码块语法,而无需在宿主语言中内置。例如,在仓颉中,我们支持下面这种方式的函数调用:

func unless(condition: Bool, f: ()->Unit) {    if(!condition) {        f()    }} let a = f(...)unless(a > 0) {    print("no greater than 0")}

这里对unless函数的调用看上去像是一种特殊的if表达式,这种语法效果是通过尾随 lambda 语法实现 —— 如果函数的最后一个形参是函数类型,那么实际调用这个函数时,我们可以提供一个 lambda 表达式作为实参,并且把它写在函数调用括号的外面。尤其当这个 lambda 表达式为无参函数时,我们允许省略 lambda 表达式中的双箭头=>,将其表示为代码块的形式,从而进一步减少对应 DSL 中的语法噪音。因此,在上面的例子中,unless调用的第二个实参就变成了这样的 lambda 表达式:

{ print("no greater than 0") }

如果函数定义只有一个参数,并且该参数是函数类型,我们使用尾随 lambda 调用该函数时还可以进一步省略函数调用的括号,从而让代码看上去更简洁自然。

func runLater(fn:()->Unit) {    sleep(5 * Duration.Second)    fn()} runLater() { // ok    println("I am later")} runLater { // 可以进一步省略括号    println("I am later")}

管道(Pipeline)操作符****

仓颉中引入管道(Pipeline)操作符,来简化嵌套函数调用的语法,更直观的表达数据流向。下面的例子中,给出了嵌套函数调用和与之等效的基于管道操作符|>的表达式。后者更加直观的反映了数据的流向:|>左侧的表达式的值被作为参数传递给右侧的函数。

| func double(a: Int) {    a * 2} func increment(a: Int) {    a + 1} double(increment(double(double(5)))) // 42 5 |> double |> double |> increment |> double // 42 | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

操作符重载****

仓颉中定义了一系列使用特殊符号表示的操作符,其中大多数操作符都允许被重载,从而可以作用在开发者自己定义的类型上,为自定义类型的操作提供更加简洁直观的语法表达。

在仓颉中只需要定义操作符重载函数就能实现操作符重载。在下面的例子中,我们首先定义一个类型Point表示二维平面中的点,然后我们通过重载+操作符,来定义两个点上的加法操作。

struct Point {    let x: Int    let y: Int     init(x: Int, y: Int) {...}     operator func +(rhs: Point): Point {        return Point(            this.x + rhs.x,            this.y + rhs.y        )    }} let a: Point = ...let b: Point = ...let c = a + b

属性(property)****

在面向对象范式中,我们常常会将成员变量设计为private的,而将成员变量的访问封装成 getter 和 setter 两种public方法。

这样可以隐藏数据访问的细节,从而更容易实现访问控制、数据监控、跟踪调试、数据绑定等业务策略。

仓颉中直接提供了属性这一种特殊的语法,它使用起来就像成员变量一样可以访问和赋值,但内部提供了 getter 和 setter 来实现更丰富的数据操作。对成员变量的访问和赋值会被编译器翻译为对相应 getter 和 setter 成员函数的调用。

具体来说,prop 用于声明只读属性,只读属性只具有 getter 的能力,必须提供 get 实现;mut prop 用于声明可变属性。可变属性同时具备 getter 和 setter 的能力,必须提供 get 和 set 实现。

如下示例所示,开发者希望对 Point 类型的各数据成员的访问进行记录,则可以在内部声明 private 修饰的成员变量,通过声明对应的属性来对外暴露访问能力,并在访问的时候使用日志系统Logger记录它们的访问信息。对使用者来说,使用对象p的属性与访问它的成员变量一样,但内部却实现了记录的功能。

注意这里x和y是只读的,只有get实现,而color则是可变的,用mut prop修饰,同时具有get和set实现。

class Point {    private let _x: Int    private let _y: Int    private var _color: String     init(x: Int, y: Int, color: String) {...}     prop x: Int {        get() {            Logger.log(level: Debug, "access x")            return _x        }    }     prop y: Int {        get() {            Logger.log(level: Debug, "access y")            return _y        }    }     mut prop color: String {        get() {            Logger.log(level: Debug, "access color")            return _color        }         set(c) {            Logger.log(level: Debug, "reset color to ${c}")            _color = c        }    }} main() {    let p = Point(0, 0, "red")    let x = p.x // "access x"    let y = p.y // "access y"    p.color = "green" // "reset color to green"}

安全可靠****

编程语言的设计和实现,以及相应工具支持,对于程序质量和安全性有重要影响。

仓颉通过静态类型系统、动静态检查、自动内存管理、以及工具链来提升程序的安全性。