表达式

表达式通常由一个或多个操作数(operand)构成,多个操作数之间由操作符(operator)连接,每个表达式都有一个类型,计算表达式值的过程称为对表达式的求值(evaluation)。

在仓颉编程语言中,表达式几乎无处不在,有表示各种计算的表达式(如算术表达式、逻辑表达式等),也有表示分支和循环的表达式(如 if 表达式、循环表达式等)。对于包含多个操作符的表达式,必须明确每个操作符的优先级、结合性以及操作数的求值顺序。优先级和结合性规定了操作数与操作符的结合方式,操作数的求值顺序规定了二元和三元操作符的操作数求值顺序,它们都会对表达式的值产生影响。

下面将依次介绍仓颉中的表达式。

注:本章中对于各操作符的操作数类型的规定,均建立在操作符没有被重载的前提下。

字面量

字面量是一种拥有固定语法的表达式。对于内部不包含其他表达式的字面量(参见 1.3 字面量),它的值就是字面量自身的值,它的类型可由其语法或所在的上下文决定。当无法确定字面量类型时,整数字面量具有 Int64 类型,浮点数字面量具有 Float64 类型。对于可在内部包含其他表达式的集合类字面量和元组字面量(参见 [值类型]),它的值等于对其内部所有表达式求值后得到的字面量的值,它的类型由其语法确定。

字面量举例:

main(): Int64 {
    10u8                          // UInt8 literal
    10i16                         // Int16 literal
    1024u32                       // UInt32 literal
    1024                          // Int64 literal
    1024.512_f32                  // Float32 literal
    1024.512                      // Float64 literal
    'a'                           // Rune literal
    true                          // Bool literal
    "Cangjie"                     // String literal
    ()                            // Unit literal
    [1, 2, 3]                     // Array<Int64> literal
    (1, 2, 3)                     // (Int64, Int64, Int64) literal
    return 0
}

变量名和函数名

变量名和函数名(这里的变量名和函数名也包括通过包名指向的变量或函数)本身也是表达式。对于变量名,它的值等于变量求值后的值,它的类型为变量的类型;对于函数名,它的值是一个闭包(见 5.7 节),它的类型为对应的函数类型。

变量名和函数名举例:

let intNum: Int64 = 100 // 'intNum' is the name of a variable, whose value and type are '100' and 'Int64', respectively.

/* 'add' is the name of a function, whose value and type are '(p1: Int64, p2: Int64) => {p1 + p2}' and '(Int64, Int64) -> Int64', respectively. */
func add(p1: Int64, p2: Int64) { 
    p1 + p2 
} 

let value = p1.x // x is a variable defined in package p1.

对于变量名,规定 var 声明的变量始终是可变的,let 声明的变量只可以被赋一次值(声明时或声明之后),赋值前是可变的,赋值后是不可变的。

泛型函数名作为表达式

仓颉语言中支持函数(见第 5 章)做为第一成员,同时也支持声明类型参数的泛型函数(见第 9 章)。当函数为泛型函数时,函数名作为表达式使用时必须给出泛型函数的类型实参。例如:

func identity<T>(a: T) { // identity is a generic function
    return a 
}

var b = identity // error: generic function 'identity' needs type arguments

var c = identity<Int32> // ok: Int32 is given as a type argument

identity 是一个泛型函数,所以identity不是合法的表达式,只有给出了类型实参的 identity<Int32>才是一个合法的表达式。

若一个函数在当前作用域中被重载了,当重载函数中存在多个类型完备的可选函数,那么直接使用该类型名作为表达式是有歧义错误的,例如:

interface Add<T> {
    operator func +(a: T): T
}

func add<T>(i: T, j: Int64): Int64 where T <: Add<Int64> { // f1
    return i + j;
}

func add<T>(i: T, j: T): T where T <: Add<T> { // f2
    return i + j;
}

main(): Int64 {
    var plus = add<Int64> // error: ambiguous use of 'add'
    return 0
}

条件表达式

条件表达式即 if 表达式,可以根据判定条件是否成立来决定执行哪条代码分支,实现分支控制逻辑。

if 表达式的语法定义为:

ifExpression
    : 'if' '(' ('let' deconstructPattern '<-')? expression ')' block ('else' ( ifExpression | block))?
    ;

其中 if 是关键字,if 之后是一个包围在小括号内的表达式,接着是一个块,块之后是可选的 else 分支。else 分支以 else 关键字开始,后接新的 if 表达式或一个块。

if 表达式举例:

main(): Int64 {
    let x = 100
    // if expression without else branch
    if (x > 0) { 
        print("x is larger than 0")
    }

    // if expression with else branch
    if (x > 0) { 
        print("x is larger than 0")
    } else {
        print("x is not larger than 0")
    }

    // if expression with nested if expression
    if (x > 0) {
        print("x is larger than 0")
    } else if (x < 0) {
        print("x is lesser than 0")
    } else {
        print("x equals to 0")
    }    
    
    return 0 
}

if 表达式首先对 if 之后的表达式进行求值(要求表达式的类型为 Bool),如果表达式的值等于 true,则执行它之后的块,否则,执行 else 之后的 if 表达式或块(如果存在)。if 表达式的值等于被执行到的分支中的表达式的值。

对于包含 letif 表达式,我们称之为 if-let 表达式。我们可以用 if-let 表达式来做一些简单的解构操作。

一个基础的 if-let 表达式举例:

main(): Int64 {
    let x: Option<Int64> = Option<Int64>.Some(100)
    // if-let expression without else branch
    if (let Some(v) <- x) { 
        print("x has value")
    }
    // if-let expression with else branch
    if (let Some(v) <- x) { 
        print("x has value")
    } else {
        print("x has not value")
    }
    return 0 
}

if-let 表达式首先对 <- 之后的表达式进行求值(表达式的类型为可以是任意类型),如果表达式的值能匹配 let 之后的 pattern,则执行它之后的块,否则,执行 else 之后的 if 表达式或块(如果存在)。if-let 表达式的值等于被执行到的分支中的表达式的值。

let 之后的 pattern 支持常量模式、通配符模式、绑定模式、Tuple 模式、enum 模式。

if 表达式的类型

对于没有 else 分支的 if 表达式,它的类型为 Unit,它的值等于 ()。因为 if expr1 {expr2}if expr1 {expr2; ()} else {()} 的语法糖。

对于包含 else 分支的 if 表达式,

  • 如果 if 表达式的值没有被读取或者返回,那么 if 表达式的类型为 Unit,两个分支不要求存在公共父类型;否则,按如下规则检查;

  • 在上下文没有明确的类型要求时,如果 if 的两个分支类型,设它们为 T1T2,则 if 表达式的类型是 T1T2 的最小公共父类型 T。如果不存在最小公共父类型 T ,则编译报错;

  • 在上下文有明确的类型要求时,此类型即为 if 表达式的类型。此时要求 if 的两个分支的类型都是上下文所要求的类型的子类型。

举例如下:

struct S1 { }
struct S2 { }

interface I1 {}
class C1 <: I1 {}
class C2 <: I1 {}

interface I2{}
class D1 <: I1 & I2 {}
class D2 <: I1 & I2 {}

func test12() {
    if (true) {  // OK. The type of this if expression is Unit.
        S1()
    } else {
        S2()
    }  

   if (true) {  // OK. The type of this if expression is Unit. 
        C1()
    } else {
        C2()
    }
    
    return if (true) {  // Error. The `if` expression is returned. There is no least common supertype of `D1` and `D2`. 
        D1()
    } else {
        D2()
    }
}

注意:为了保持代码格式的规整以及提高代码的可维护性,同时为了避免悬垂 else(dangling-else)问题,仓颉编程语言要求每个 if 分支和 else 分支中的执行部分(即使只有一条表达式)必须使用一对花括号括起来成为一个块。(悬垂 else 问题是指无法确定形如 if cond1 if cond2 expr1 else expr2 的代码中的 else expr2 是归属于内层 if 还是外层 if 的问题。如果其归属于内层 if,则代码应解读为 if cond1 (if cond2 expr1 else expr2);如果其归属于外层 if,则代码应解读为 if cond1 (if cond2 expr1)expr2。但如果强制分支使用大括号,则无此问题。)

模式匹配表达式

仓颉编程语言中支持使用模式匹配表达式(match 表达式)实现模式匹配(pattern matching),允许开发者使用更精简的代码描述复杂的分支控制逻辑。直观上看,模式描述的是一种结构,这个结构定义了一个与之匹配的实例集合,模式匹配就是去判断给定的实例是否属于模式定义的实例集合。显然,匹配的结果只有两种:匹配成功和匹配失败。match 表达式可分为两类:带 selectormatch 表达式和不带selectormatch 表达式。

match 表达式的语法定义为:

matchExpression
    : 'match' '(' expression ')' '{' matchCase+ '}'
    | 'match' '{' ('case' (expression | '_') '=>' expressionOrDeclaration+)+ '}'
    ;
matchCase
    :  'case' pattern ('|' pattern)* patternGuard? '=>' expressionOrDeclaration+
    ;
patternGuard
    : 'where' expression
    ;

对于带 selectormatch 表达式,关键字 match 之后的 expression 即为待匹配的 selectorselector 之后的{} 内可定义若干 matchCase,每个 matchCase 以关键字 case 开头,后跟一个 pattern 或者多个由 | 分隔的相同种类的 pattern(关于不同 pattern 的详细定义,见[模式]节),一个可选的 pattern guard,一个胖箭头 => 和一系列(至少一个)声明或表达式(多个声明或表达式之间使用分号或换行符分隔)。

在执行 match 表达式的过程中,匹配顺序即 case 定义的顺序,selector 按照匹配顺序依次和 case 中定义的 pattern 进行匹配,一旦 selector 和当前 pattern 匹配成功(且满足 pattern guard),则执行 => 之后的代码,且无需再与它之后的 pattern 进行匹配;否则(与当前 pattern 不匹配),继续与下一个 pattern 进行匹配判断,依次类推。下面的例子展示了 match 表达式中使用常量模式进行分支控制:

let score: Int64 = 90
var scoreResult: String = match (score) {
    case 0 => "zero"
    case 10 | 20 | 30 | 40 | 50 => "fail"
    case 60 => "pass"
    case 70 | 80 => "good"
    case 90 | 100 => "excellent" // matched
    case _ => "not a valid score"
}

出于安全和完备的考虑,仓颉编程语言要求 case 表达式中定义的所有 pattern 和其对应的 patternGuard(如果存在)组合起来要覆盖 selector 的所有可能取值(即 exhaustive),如果编译器判断出未实现完全覆盖,则会报错。为实现完全覆盖,通常可以在最后一个 case 中使用通配符 _ 来处理其他 case 未覆盖到的情况。另外,不要求每个 pattern 定义的空间是互斥的,即不同 pattern 之间覆盖的空间可以有重叠。

对于不带 selectormatch 表达式,关键字 match 之后没有 expression,并且 {} 中的每条 case 中,关键字 case=> 之间只能为一个 Bool 类型的表达式(或者通配符 _,表示永远为 true)。

在执行的过程中,依次判断 case 之后的表达式的值,一旦表达式的值等于 true,则执行 => 之后的代码,且无需再判断它之后的所有 case。事实上,不带 selectormatch 表达式其实是一连串嵌套 if-else if 表达式的简洁表达。

同样地,要求不带 selectormatch 表达式满足 exhaustive(即任何情况下至少有一个 case 是满足的)。编译器会尽量做 exhaustive 检查,如果无法判断,则报错并提示添加 case _

let score = 80 
var result: String = match {
    case score < 60 => "fail"
    case score < 70 => "pass"
    case score < 90 => "good" // matched
    case _ => "excellent"
}

类似于 if 表达式,对于 match 表达式,无论其是否有 selector,它的类型遵循如下规则:

  • 如果 match 表达式的值没有被读取或者返回,那么 match 表达式的类型为 Unit,所有分支不要求存在公共父类型;否则,按如下规则检查;

  • 在上下文没有明确的类型要求时,假设 match 的所有分支类型分别为 T1, ..., Tn,则 match 表达式的类型是 T1, ..., Tn 的最小公共父类型 T,如果不存在最小公共父类型 T 则报错。

  • 在上下文有明确的类型要求时,此类型即为 match 表达式的类型。此时要求每条 case=> 之后的表达式的类型都是上下文所要求的类型的子类型。

模式

仓颉编程语言提供了丰富的模式种类,包括:

  1. 常量模式(constant patterns);

  2. 通配符模式(wildcard patterns);

  3. 绑定模式(binding patterns);

  4. tuple 模式(tuple patterns);

  5. 类型模式(type patterns);

  6. enum 模式(enum patterns);

pattern 的语法定义为:

pattern
    : constantPattern
    | wildcardPattern
    | varBindingPattern
    | tuplePattern
    | typePattern
    | enumPattern
    ;

常量模式

常量模式可以是整数字面量、字节字面量、浮点数字面量、Rune 字面量、布尔字面量、字符串字面量(不支持字符串插值)、Unit 字面量。常量模式中字面量的类型需要和 selector 的类型一致,selector 和一个常量模式匹配成功的条件是 selector 与常量模式中的字面量相等(这里指值相等)。

常量模式的语法定义为:

constantPattern
    : literalConstant
    ;

使用常量模式的例子如下:

func f() {
    let score: Int64 = 90
    var scoreResult: String = match (score) { 
        case 0 => "zero"
        case 10 | 20 | 30 | 40 | 50 => "fail"
        case 70 | 80 => "good"
        case 90 => "excellent" // matched
        case _ => "not a valid score"
    }
}

需要注意的是,浮点数字面量匹配在常量模式中遵循浮点数的判等规则,会和判等存在一样的精度问题。

通配符模式

通配符模式使用下划线 _ 表示,它可以匹配任意值,常用于部分匹配(例如作为占位符)或作为 match 表达式的最后一个 pattern 来匹配其它 case 未覆盖到的情况。

通配符模式的语法定义为:

wildcardPattern
    : '_'
    ;

使用通配符模式的例子如下:

let score: Int64 = 90
var scoreResult: String = match (score) {
    case 60 => "pass"
    case 70 | 80 => "good"
    case 90 | 100 => "excellent" // matched
    case _ => "fail"             // wildcard pattern: used for default case 
}

绑定模式

绑定模式同样可以匹配任意值,但与通配符模式不同的是,绑定模式会将匹配到的值绑定到 binding pattern 中定义的变量,以便在 => 之后的表达式中进行访问。

绑定模式的语法定义为:

varBindingPattern
    : identifier
    ;

绑定模式中定义的变量是不可变的。

使用绑定模式的例子如下:

let score: Int64 = 90
var scoreResult: String = match (score) {
    case 60 => "pass"
    case 70 | 80 => "good"
    case 90 | 100 => "excellent" // matched
    case failScore =>            // binding pattern
        let passNeed = 60 - failScore
        "failed with ${failScore}, and ${passNeed} need to pass"
}

绑定模式中定义的变量,作用域从第一次出现的位置处开始至下一个 case 之前。需要注意,使用 | 连接多个模式时不能使用绑定模式,也不可嵌套出现在其它模式中,否则会报错。

let opt = Some(0)
match (opt) {
    case x | x => {}                // error: variable cannot be introduced in patterns connected by '|'
    case Some(x) | Some(x) => {}    // error: variable cannot be introduced in patterns connected by '|'
    case x: Int64 | x: String => {} // error: variable cannot be introduced in patterns connected by '|'
}

Tuple 模式

tuple pattern 用于匹配 Tuple 值,tuple pattern 定义为由圆括号括起来的多个 pattern,每个 pattern 之间使用逗号分隔:(pattern_1, pattern_2, … pattern_k)。例如,(x, y, z) 是由三个 binding pattern 组成的一个 tuple pattern(1, 0, 0) 是由三个 constant pattern 组成的一个 tuple patterntuple pattern 中的子 pattern 个数需要和 selector 的维度相同,并且如果子 patternconstant patternenum pattern 时,其类型要和 selector 对应维度的类型相同。

tuple 模式的语法定义为:

tuplePattern
    : '(' pattern (',' pattern)+ ')' 
    ;

使用 tuple 模式的例子如下:

let scoreTuple = ("Allen", 90)
var scoreResult: String = match (scoreTuple) { 
    case ("Bob", 90) =>  "Bob got 90"
    case ("Allen", score) =>  "Allen got ${score}" // matched
    case ("Allen", 100) | ("Bob", 100) =>  "Allen or Bob got 100"
    case (_, _) =>  ""
}

类型模式

使用类型模式可以很方便地实现 type checktype cast。类型模式的语法定义为:

typePattern
  : (wildcardPattern | varBindingPattern) ':' type
  ;

对于类型模式 varBindingPattern : type(或 wildcardPattern : type)。首先判断要匹配的值的运行时类型是否是 : 右侧 type 定义的类型或它的子类,若类型匹配成功则将值的类型转换为 type 定义的类型,然后将新类型的值与 : 左侧的 varBindingPattern 进行绑定(对于 wildcardPattern : type,不存在绑定)。只有类型匹配,才算成功匹配,否则匹配失败,因此,varBindingPattern : type(或 wildcardPattern : type)可以同时实现 type testtype cast

使用类型模式匹配的例子如下:

open class Point { 
    var x: Int32 = 1
    var y: Int32 = 2
    init(x: Int32, y: Int32) {
        this.x = x
        this.y = y
    }
}
class ColoredPoint <: Point { 
    var color: String = "green"
    init(x: Int32, y: Int32, color: String) {
        super(x, y)
        this.color = color
    }
}
let normalPt = Point(5,10)
let colorPt = ColoredPoint(8,24,"red")
var rectangleArea1: Int32 = match (normalPt) {
    case _: Point => normalPt.x * normalPt.y  // matched
    case _ => 0
}
var rectangleArea2: Int32 = match (colorPt) {
    case cpt: Point => cpt.x * cpt.y          // matched
    case _ => 0
}

enum 模式

enum 模式主要和 enum 类型配合使用。

enum pattern 用于匹配 enum constructor,格式是 constructorName(无参构造器)或 constructorName(pattern_1, pattern_2, ..., pattern_k)(有参构造器),圆括号内用逗号分隔的若干 pattern(可以是其它任何类型的 pattern,并允许嵌套)依次对每个参数进行匹配。

enum 模式的语法定义为:

enumPattern
   : (userType '.')? identifier enumPatternParameters?
   ;
enumPatternParameters
   : '(' pattern (',' pattern)* ')'
   ;

使用enum模式匹配的例子如下:

enum TimeUnit { 
    | Year(Float32)
    | Month(Float32, Float32)
    | Day(Float32, Float32, Float32)
    | Hour(Float32, Float32, Float32, Float32)
}
let oneYear = TimeUnit.Year(1.0) 
var howManyHours: Float32 = match (oneYear) {
    case Year(y) => y * Float32(365 * 24) // matched
    case Month(y, m) => y * Float32(365 * 24) + m * Float32(30 * 24)
    case Day(y, m, d) => y * Float32(365 * 24) + m * Float32(30 * 24) + d * Float32(24)
    case Hour(y, m, d, h) => y * Float32(365 * 24) + m * Float32(30 * 24) + d * Float32(24) + h
}

let twoYear = TimeUnit.Year(2.0)
var howManyYears: Float32 = match (twoYear) {
    case Year(y) | Month(y, m) => y // error: variable cannot be introduced in patterns connected by '|'
    case Year(y) | Month(x, _) => y // error: variable cannot be introduced in patterns connected by '|'
    case Year(y) | Month(y, _) => y // error: variable cannot be introduced in patterns connected by '|'
    ...
}

如果模式匹配所在的作用域中,某个 pattern 中的 identifier 是 enum 构造器时,该 identifier 总是会被当成 enum pattern 进行匹配,否则才会作为 binding pattern 匹配。

enum Foo {
    A | B | C
}

func f() {
    let x = Foo.A
    match (x) {
        case A => 0 // enum pattern
        case B => 1 // enum pattern
        case C => 2 // enum pattern
        case D => 3 // binding pattern
    }
}

需要注意的是,对 enum 进行匹配时,要求 enum pattern 的类型和 selector 的类型相同,同时编译器会检查 enum 类型的每个 constructor(包括 constructor 的参数的值)是否被完全覆盖,如果未做到全覆盖,则编译器会报错。

enum TimeUnit { 
    | Year(Float32)
    | Month(Float32, Float32)
    | Day(Float32, Float32, Float32) 
    | Hour(Float32, Float32, Float32, Float32)
}

let oneYear = TimeUnit.Year(1.0)
var howManyHours: Float32 = match (oneYear) { // error: match must be exhaustive
    case Year(y) => y * Float32(365 * 24)
    case Month(y, m) => y * Float32(365 * 24) + m * Float32(30 * 24)
}

模式的分类

一般地,在类型匹配的前提下,当一个 pattern 有可能和它所要匹配的值不匹配时,称此 pattern 为 refutable pattern;反之,当一个 pattern 总是可以和它所要匹配的值匹配时,称此 pattern 为 irrefutable pattern。对于上述介绍的各类 pattern,规定:

  • constant pattern 总是 refutable pattern
  • wildcard pattern 总是 irrefutable pattern
  • binding pattern 总是 irrefutable pattern
  • tuple pattern 是 irrefutable pattern,当且仅当其包含的每个 pattern 都是 irrefutable pattern
  • type pattern 总是 refutable pattern
  • enum pattern 是 irrefutable pattern,当且仅当其对应的 enum 类型中只有一个带参 constructor,且 enum pattern 中包含的其他 pattern(如果存在)都是 irrefutable pattern

字符串、字节和 Rune 的匹配规则

在模式匹配的目标是静态类型为 Rune 的值时,Rune 字面量和单字符字符串字面量都可用于表示 Rune 类型字面量的常量 pattern。

在模式匹配的目标是静态类型为 Byte 的值时,一个表示 ASCII 字符的字符串字面量可用于表示 Byte 类型字面量的常量 pattern。

Pattern Guards

为了对匹配出来的值做进一步的判断,仓颉支持使用 pattern guardpattern guard 可以在 match 表达式中使用,也可以在 for-in 表达式中使用。本节主要介绍 pattern guard 在 match 表达式中的使用,关于其在 for in 表达式中的使用请参见[for-in 表达式]。

match 表达式中,为了提供更加强大和精确的匹配模式,支持 pattern guard,即在 pattern=> 之间加上 where boolExpressionboolExpression是值为布尔类型的表达式)。匹配的过程中,只有当值与 pattern 匹配并且满足 where 之后的 boolExpression 时,整个 case 才算匹配成功,否则匹配失败。

pattern guard 的语法定义为:

patternGuard
    : 'where' expression 
    ;

使用 pattern guards 的例子如下:

let oneYear = Year(1.0) 
var howManyHours: Float32 = match (oneYear) {
    case Year(y) where y > 0.0f32 => y * Float32(365 * 24) // matched
    case Year(y) where y <= 0.0f32 => 0.0
    case Month(y, m) where y > 0.0f32 && m > 0.0f32 => y * Float32(365 * 24) + m * Float32(30 * 24)
    case Day(y, m, d) where y > 0.0f32 && m > 0.0f32 && d > 0.0f32 => y * Float32(365 * 24) + m * Float32(30 * 24) + d * Float32(24)
    case Hour(y, m, d, h) where y > 0.0f32 && m > 0.0f32 && d > 0.0f32 && h > 0.0f32 => y * Float32(365 * 24) + m * Float32(30 * 24) + d * Float32(24) + h
    case _ => 0.0
}

循环表达式

仓颉编程语言支持三种循环表达式:for-in 表达式、while 表达式和 do-while 表达式。

循环表达式的语法定义为:

loopExpression
  : forInExpression
  | whileExpression
  | doWhileExpression
  ;

for-in 表达式

一个完整的 for-in 表达式 具有如下形式:

for (p in e where c) {
    s
}

其中 pattern guard where c 是非必须的,因此更简易的 for-in 表达式 具有如下形式:

for (p in e) {
    s
}

for-in 表达式 的语法定义为:

forInExpression
    : 'for' '(' patternsMaybeIrrefutable 'in' expression patternGuard? ')' block
    ;
    
patternsMaybeIrrefutable
    : wildcardPattern
    | varBindingPattern
    | tuplePattern
    | enumPattern
    ;

patternGuard
    : 'where' expression
    ;

上述语法定义中,关键字 for 之后只能是那些一定或可能为 irrefutable 的 pattern(见 [模式的分类])。在语义检查阶段,会检查 for 之后的 pattern 是否真的是 irrefutable,如果不是 irrefutable pattern,则编译报错。另外,如果 for 之后的 pattern 中存在 binding pattern,相当于新声明了一个(或多个) let 变量,每个变量的作用域从它第一次出现的位置到循环体结束。

for-in 会先对 expression 求值,再调用其 iterator() 函数,获取一个类型为 Iterator<T> 的值。程序通过调用 Iterator<T>next() 函数开始执行循环,我们可以使用 pattern 匹配迭代的元素,如果匹配成功(如果存在 patternGuard,也必须同时满足 patternGuard 的条件),则执行循环体 block,然后在开始处重新调用 next() 继续循环,当 next() 返回 None 时循环终止。

Iterable<T>Iterator<T> 可在标准库中查阅。

main(): Int64 {
    let intArray: Array<Int32> = [0, 1, 2, 3, 4]
    for (item in intArray) {
        print(item)          // output: 01234
    }

    let intRange = 0..5
    for (number in intRange where number > 2) {
        print(number)        // output: 34
    }

    return 0
}

while 表达式

while 表达式的语法定义为:

whileExpression
    : 'while' '(' ('let' deconstructPattern '<-')? expression ')' block
    ;

其中 while 是关键字,while 之后是一个小括号,小括号内可以是一个表达式或者一个 let 声明的解构匹配,接着是一个块。

一个基础的 while 表达式举例:

main(): Int64 {
    var hundred = 0
    while (hundred < 100) { // until hundred = 100
        hundred++
    }
    return 0
}

while 表达式首先对 while 之后的表达式进行求值(要求表达式的类型为 Bool),如果表达式的值等于 true,则执行它之后的块,接着重新计算表达式的值并判断是否重新执行一次循环;如果表达式的值等于 false,则终止循环。

对于包含 letwhile 表达式,我们称之为 while-let 表达式。我们可以用 while-let 表达式来做一些简单的解构操作。

一个基础的 while-let 表达式举例:

main(): Int64 {
    var x: Option<Int64> = Option<Int64>.Some(100)
    // while-let expression
    while (let Some(v) <- x) { 
        print("x has value")
        x = ...
    }
    return 0 
}

while-let 表达式首先对 <- 之后的表达式进行求值(表达式的类型为可以是任意类型),如果表达式的值能匹配 let 之后的 pattern,则执行它之后的块,接着重新计算表达式的值然后再次匹配并判断是否重新执行一次循环;如果匹配失败,则终止当前的 while 循环。

let 之后的 pattern 支持常量模式、通配符模式、绑定模式、Tuple 模式、enum 模式。

do-while 表达式

do-while 表达式的语法定义为:

doWhileExpression
    : 'do' block 'while' '(' expression ')' 
    ;

while 表达式不同的是:while 表达式在第一次循环迭代时,如果表达式expression 的值为 false,则循环体不会被执行;然而对于 do-while 表达式,第一次循环迭代时,先执行循环体 block,然后再根据表达式 expression 的值决定是否再次执行循环体,也就是说 do-while 表达式中的循环体会至少执行一次。例如:

main(): Int64 {
    var hundred = 0
    do {
        hundred++
    } while (hundred < 100)
    return 0
}

循环表达式总结

for-inwhiledo-while 这三种循环表达式的表达能力是等价的,通常,在知道循环的次数或遍历一个序列中的所有元素时使用 for-in 表达式;在不知道循环的次数,但知道循环终止条件时使用 whiledo-while 表达式。

三种循环表达式的类型均为 Unit

由于 breakcontinue 表达式必须有包围着它们的循环体,所以对于三种循环表达式,其循环条件中出现的 breakcontinue 均会绑定到其最近的外层循环;如外层不存在循环,则报错。例如

while (true) {
	println("outer") // printed once
	do {
		println("inner") // printed once
	} while (break)  // stop the execution of the outer loop
	println("unreached") // not printed
}

try 表达式

根据是否涉及资源的自动管理,将 try 表达式分为两类:不涉及资源自动管理的普通 try 表达式,以及会进行资源自动管理的 try-with-resources 表达式。

try 表达式的语法为:

tryExpression
    : 'try' block 'finally' block
    | 'try' block ('catch' '(' catchPattern ')' block)+ ('finally' block)?
    | 'try' '(' resourceSpecifications ')' block ('catch' '(' catchPattern ')' block)* ('finally' block)?
    ;

普通 try 表达式 的主要目的是错误处理,详见[异常]章节。

try-with-resources 表达式的主要目的是自动释放非内存资源,详见[异常]章节。

控制转移表达式

控制转移表达式会改变程序的执行顺序。控制转移表达式的类型是 Nothing 类型,该类型是任何类型的子类型。仓颉编程语言提供如下控制转移表达式:

  • break
  • continue
  • return
  • throw

控制转移表达式可以像其他表达式一样,作为子表达式成为复杂表达式的一部分,但是有可能会导致产生不可达代码(不可达部分会编译告警):

main(): Int64 {
    return return 1 // warning: the left return expression is unreachable
}

控制转移表达式中,breakcontinue 必须有包围着它们的循环体,且该循环体无法穿越函数边界;return 必须有包围着它的函数体,且该函数体无法穿越;对 throw 不作要求。

“包围着的循环体”无法穿越“函数边界”。在下面的例子中,break 出现在函数 f 中,外层的 while 循环体不被视作包围着它的循环体;continue 出现在 lambda 表达式 中,外层的 while 循环体不被视作包围着它的循环体。

while (true) {
    func f() {
        break // Error: break must be used directly inside a loop
    }
    let g = { =>
        continue // Error: continue must be used directly inside a loop
    }
}

控制转义表达式的语法为:

jumpExpression
  : 'break'
  | 'continue'
  | 'return' expression?
  | 'throw'  expression
  ;

break 表达式

break表达式只能出现在循环表达式的循环体中,并将程序的执行权交给被终止循环表达式之后的表达式。例如下面的代码通过在while循环体内使用break表达式,实现在区间[1,49]内计算46的最小公倍数。

main(): Int64 {
    var index: Int32 = 0
    while (index < 50) {
        index = index + 1
        if ((index % 4 == 0) && (index % 6 == 0)) {
            print("${index} is divisible by both 4 and 6") // output: 12
            break
        }
    }
    return 0
}

需要注意的是,当break出现在嵌套的循环表达式中时,只能终止直接包围它的循环表达式,外层的循环并不会受影响。例如下面的程序将输出 5 次12 is divisible by both 4 and 6,且每次同时会输出i的值:

main(): Int64 {
    var index: Int64 = 0
    for (i in 0..5) {
        index = i
        while (index < 20) {
            index = index + 1
            if ((index % 4 == 0) && (index % 6 == 0)) {
                print("${index} is divisible by both 4 and 6")
                break
            }    
        }
        print("${i}th test")
    }
    return 0
}

continue 表达式

continue表达式只能出现在循环表达式的循环体中,用于提前结束离它最近循环表达式的当前迭代,然后开始新一轮的循环(并不会终止循环表达式)。例如下面的代码输出区间[1,49]内所有可以同时被46整除的数(12243648),对于其他不满足要求的数,同样会显式地输出。

main(): Int64 {
    var index: Int32 = 0
    while (index < 50) {
        index = index + 1
        if ((index % 4 == 0) && (index % 6 == 0)) {
            print("${index} is divisible by both 4 and 6")
            continue
        }
        print("${index} is not what we want")
    }
    return 0
}

return 表达式

return表达式只能出现在函数体中,它可以在任意位置终止函数的执行并返回,实现控制流从被调用函数到调用函数的转移。return表达式有两种形式:returnreturn exprexpr是一个表达式)。

  • 若为return expr的形式,我们将expr的值作为函数的返回值,所以要求expr的类型与函数定义中的返回类型保持一致。
// return expression
func larger(a: Int32, b: Int32): Int32 {
    if (a >= b) {
        return a
    } else {
        return b
    }
}
  • 若为return的形式,我们将其视为return ()的语法糖,所以要求函数的返回类型也为Unit
// return expression
func equal(a: Int32, b: Int32): Unit {
    if (a == b) { 
        print("a is equal to b")
        return
    } else {
        print("a is not equal to b")
    }     
}

需要说明的是,return表达式作为一个整体,其类型并不由后面跟随的表达式决定(return 后面跟随的表达式为 ()),而是Nothing类型。

throw 表达式

throw表达式用于抛出异常,在调用包含throw表达式的代码块时,如果throw表达式被执行到,就会抛出相应的异常,并由事先定义好的异常处理逻辑进行捕获和处理,从而改变程序的执行流程。

下面的例子中,当除数为 0 时,抛出算术异常:

func div(a: Int32, b: Int32): Int32 {
    if (b != 0) {
        return a / b
    } else {
        throw ArithmeticException()
    }     
}

关于returnthrow表达式,本节只做了最简单的使用举例,有关它们的详细介绍,请分别参见函数和异常章节。

数值类型转换表达式

数值类型转换表达式用于实现数值类型间的转换,它的值是类型转换后的值,它的类型是转换到的目标类型(但原表达式的类型不受目标类型影响),详细的转换规则可参见 [类型转换]。

数值类型转换表达式的语法定义为:

numericTypeConvExpr
    : numericTypes '(' expression ')'
    ;
    
numericTypes
    : 'Int8'
    | 'Int16'
    | 'Int32'
    | 'Int64'
    | 'UInt8'
    | 'UInt16'
    | 'UInt32'
    | 'UInt64'
    | 'Float16'
    | 'Float32'
    | 'Float64'
    ;

this 和 super 表达式

thissuper 表达式分别使用 thissuper 表示,this 可以出现在所有实例成员函数和构造函数中,表示当前实例, super只能出现在 class 类型定义中,表示当前定义的类型的直接父类的实例(详见[类])。禁止使用单独的 super 表达式。

thissuper 表达式的语法定义为:

thisSuperExpression
    : 'this'
    | 'super'
    ;

spawn 表达式

spawn 表达式用于创建并启动一个 thread,详见[并发]章节。

synchronized 表达式

synchronized 表达式用于同步机制中,详见[并发]章节。

括号表达式

括号表达式是指使用圆括号括起来的表达式。圆括号括起来的子表达式被视作一个单独的计算单元被优先计算。

括号表达式的语法定义为:

parenthesizedExpression
    : '(' expression ')'
    ;

括号表达式举例:

1 + 2 * 3 - 4    // The result is 3.
(1 + 2) * 3 - 4  // The result is 5.

后缀表达式

后缀表达式由表达式加上后缀操作符构成。根据后缀操作符的不同,分为:成员访问表达式、函数调用表达式、索引表达式。在成员访问表达式、函数调用表达式、索引表达式中的后缀操作符的前面,可以使用可选的 ? 操作符,以实现 Option 类型对这些后缀操作符的支持。关于 ? 操作符,详见下文介绍。

后缀表达式的语法定义为:

postfixExpression
    : atomicExpression
    | type '.' identifier
    | postfixExpression '.' identifier typeArguments?
    | postfixExpression callSuffix
    | postfixExpression indexAccess
    | postfixExpression '.' identifier callSuffix? trailingLambdaExpression
    | identifier callSuffix? trailingLambdaExpression
    | postfixExpression ('?' questSeperatedItems)+
    ;

成员访问表达式

成员访问表达式的语法定义为上述后缀表达式语法的第 3 条:

postfixExpression '.' identifier typeArguments?

成员访问表达式可以用于访问 class、interface、struct 等的成员。

成员访问表达式的形式为T.a T 可以表示为特定的实例或类型名,我们将其称为成员访问表达式的主体。a 表示成员的名字。

  • 如果T是类的实例化对象,通过这种方式可以访问类或接口中的非静态成员。
  • 如果Tstruct 的实例,允许通过实例名访问 struct 内的非静态成员。
  • 如果T是类名、接口名或 struct 名,允许直接通过类型名访问其静态成员。

需要注意的是:类、接口和 struct 的静态成员的访问主体只能是类型名。

  • Tthis:在类或接口的作用域内,可以通过this关键字访问非静态成员。
  • Tsuper:在类或接口作用域内,可以通过super关键字访问当前类对象直接父类的非静态成员。

对于成员访问表达式 e.a,如果 e 是类型名:

  • ae可变静态成员变量时,e.a可变的,其他情况下 e.a不可变的。

如果 e 是表达式(假设 e 的类型是 T):

  • T 是引用类型时,如果 aT可变实例成员变量,则 e.a可变的,否则 e.a不可变的;

  • T 是值类型时,如果 e 可变aT可变实例成员变量,则 e.a可变的,否则 e.a不可变的。

函数调用表达式

函数调用表达式的语法定义为上述后缀表达式语法的第 4 条,其中 callSuffixvalueArgument 的语法定义为:

callSuffix
    : '(' (valueArgument (',' valueArgument)*)? ')'
    ;
    
valueArgument
    : identifier ':' expression
    | expression
    | refTransferExpression
    ;

refTransferExpression
    : 'inout' (expression '.')? identifier
    ;

函数调用表达式用于调用函数,函数详见第 5 章。

对于函数调用表达式 f(x),假设f的类型是T。如果T是函数类型,则调用名为f的函数,否则,如果T重载了函数调用操作符() ,则f(x)会调用其()操作符重载函数(参见 [可以被重载的操作符])。

索引访问表达式

索引访问表达式的语法定义为上述后缀表达式语法的第 5 条,其中 indexAccess 的语法定义为:

indexAccess
    : '[' (expression | rangeElement) ']'
    ;

rangeElement
    :  '..'
    | ('..=' | '..' ) expression
    | expression '..'
    ;

索引访问表达式用于那些支持索引访问的类型(包括 Array 类型和 Tuple 类型)通过下标来访问其具体位置的元素,详见第 2 章中关于 Array 类型和 Tuple 类型的介绍。

对于索引访问表达式 e[a](假设 e 的类型是 T):

  • T 是元组类型时,e[a]不可变的;

  • T 不是元组类型时,如果 T 重载了 set 形式的操作符 [](参见 [可以被重载的操作符]),则 e[a]可变的,否则 e[a]不可变的。

对于索引访问表达式 e1[e2],仓颉语言总是先求值 e1v1,再将 e2 求值至 v2,最后根据下标 v2 选取对应的值或调用相应的重载了的 [] 操作符。

问号操作符

问号操作符 ? 为一元后缀操作符,它必须和上文介绍的后缀操作符 .(){}[] 一起使用(出现在后缀操作符之前),实现 Option 类型对这些后缀操作符的支持,例如:a?.ba?(b)a?[b]等等。其中()是函数调用,当函数调用最后一个实参是 lambda 时,可以使用尾闭包语法 a?{b}。将包含 ?.?()?{}?[] 的表达式称为 optional chaining 表达式。

optional chaining 表达式的语法定义为上述后缀表达式语法的最后一条,其中 questSeperatedItems 的语法定义为:

questSeperatedItems
    : questSeperatedItem+
    ;

questSeperatedItem
    : itemAfterQuest (callSuffix | callSuffix? trailingLambdaExpression | indexAccess)?
    ;

itemAfterQuest
    : '.' identifier typeArguments?
    | callSuffix
    | indexAccess
    | trailingLambdaExpression
    ;

关于 optional chaining 表达式,规定:

  1. 对于表达式 e,将 e 中的所有 ? 删除,并且将紧邻 ? 之前的表达式的类型由 Option<T> 替换为 T 之后,得到表达式 e1。如果 e1 的类型是 Option 类型,则在 e 之后使用 .(){}[] 时,需要在 e 和这些操作符之间加上 ?;否则,不应该加 ?

  2. Optional chaining 表达式的类型是 Option<T>(即无论其中有几个 ?,类型都只有一层 Option),类型 T 为 optional chaining 中最后一个表达式(变量或函数名、函数调用表达式、下标访问表达式)的类型;

  3. 一旦 optional chaining 中的某个 Option 类型的表达式的值为 None,则整个 optional chaining 表达式的值为 None;如果 optional chaining 中每个 Option 类型的表达式的值都等于某个 Some 值,则整个表达式的值为 Some(v)v 的类型是最后一个表达式的类型)。

以表达式 a?.bc?(d)e?[f] 为例,说明如下:

  1. 表达式 a 的类型需要是某个 Option<T1>T1 包含实例成员 b;表达式 c 的类型需要是某个 Option<(T2)->U2>d 的类型为 T2;表达式 e 的类型需要是某个 Option<T3>T3 支持下标操作符;

  2. 表达式 a?.bc?(d)e?[f] 的类型分别为 Option<U1>Option<U2>Option<U3>,其中 U1T1 中实例成员 b 的类型,U2 是函数类型 (T2)->U2 的返回值类型,U3T3 执行下标操作的返回类型;

  3. ace 的值分别等于 Some(v1)Some(v2)Some(v3) 时,a?.bc?(d)e?[f] 的值分别等于 Option<U1>.Some(v1.b)Option<U2>.Some(v2(d))Option<U3>.Some(v3[f]);当 ace 的值分别等于 None 时,a?.bc?(d)e?[f] 的值分别等于 Option<U1>.NoneOption<U2>.NoneOption<U3>.None(注意这里的 bdf 都不会被求值)。

事实上,表达式 a?.bc?(d)e?[f] 分别等价于如下 match 表达式:

// a?.b is equivalent to the following match expression.
match (a) {
    case Some(v) => Some(v.b)
    case None => None<U1>
}

// c?(d) is equivalent to the following match expression.
match (c) {
    case Some(v) => Some(v(d))
    case None => None<U2>
}

// e?[f] is equivalent to the following match expression.
match (e) {
    case Some(v) => Some(v[f])
    case None => None<U3>
}

再来看一个包含多个 ? 的多层访问的例子 a?.b.c?.d(以 ?. 为例,其他操作类似,不再赘述):

  1. 表达式 a 的类型需要是某个 Option<Ta>Ta 包含实例成员 bb 的类型中包含实例成员变量 cc 的类型是某个 Option<Tc>Tc 包含实例成员 d

  2. 表达式 a?.b.c?.d 的类型为 Option<Td>,其中 TdTc 的实例成员 d 的类型;

  3. a 的值等于 Some(va)va.b.c 的值等于 Some(vc) 时,a?.b.c?.d 的值等于 Option<Td>.Some(vc.d);当 a 的值等于 Some(va)va.b.c 的值等于 None 时,a?.b.c?.d 的值等于 Option<Td>.Noned 不会被求值);当 a 的值等于 None 时,a?.b.c?.d 的值等于 Option<Td>.Nonebcd 都不会被求值)。

表达式 a?.b.c?.d 等价于如下 match 表达式:

// a?.b.c?.d is equivalent to the following match expression.
match (a) {
    case Some(va) =>
        let x = va.b.c
        match (x) {
            case Some(vc) => Some(vc.d)
            case None => None<Td>
        }
    case None =>
        None<Td>
}

Optional chaining 也可以作为左值表达式(参见 [赋值表达式]),例如 a?.b = e1a?[b] = e2a?.b.c?.d = e3 等。

赋值表达式的左侧是 optional chaining 表达式时,要求 optional chaining 表达式是可变的(参见 [赋值表达式])。因为函数类型是不可变的,所以只需要考虑 ?.?[] 这两种情况,并且它们都可以归纳到 a?.b = ca?[b] = c 这两种基本的场景(假设 a 的类型为 Option<T>),规定:

  • 对于 a?.b,仅当 T 是引用类型且 b 可变时,a?.b可变的,其他情况下 a?.b不可变的。

  • 对于 a?[b],仅当 T 是引用类型且重载了 set 形式的操作符 [] 时,a?[b]可变的,其他情况下 a?[b]不可变的。

a?.b(或 a?[b]可变时,如果 a 的值等于 Option<T>.Some(v),将 c 的值赋值给 v.b (或 v[b]);如果 a 的值等于 Option<T>.None,什么也不做(bc 也不会被求值)。

类似地,表达式 a?.b = e1a?[b] = e2a?.b.c?.d = e3 分别等价于如下 match 表达式。

// a?.b = e1 is equivalent to the following match expression.
match (a) {
    case Some(v) => v.b = e1
    case None => ()
}

// a?[b] = e2 is equivalent to the following match expression.
match (a) {
    case Some(v) => v[b] = e2
    case None => ()
}

// a?.b.c?.d = e3 is equivalent to the following match expression.
match (a) {
    case Some(va) => 
        match (va.b.c) {
            case Some(vc) => vc.d = e3
            case None => ()
        }
    case None => 
    	()
}

操作符 ? 应用举例:

// The usuage of ?.
class C {
    var item: Int64 = 100
}
let c = C()
let c1 = Option<C>.Some(c)
let c2 = Option<C>.None
let r1 = c1?.item              // r1 = Option<Int64>.Some(100)
let r2 = c2?.item              // r2 = Option<Int64>.None
func test1() {
    c1?.item = 200             // c.item = 200
    c2?.item = 300             // no effect
}

// The usuage of ?()
let foo = {i: Int64 => i + 1}
let f1 = Option<(Int64) -> Int64>.Some(foo)
let f2 = Option<(Int64) -> Int64>.None
let r3 = f1?(1)               // r3 = Option<Int64>.Some(2)
let r4 = f2?(1)               // r4 = Option<Int64>.None

// The usuage of ?[] for tuple access
let tuple = (1, 2, 3)
let t1 = Option<(Int64, Int64, Int64)>.Some(tuple)
let t2 = Option<(Int64, Int64, Int64)>.None
let r7 = t1?[0]               // r7 = Option<Int64>.Some(1)
let r8 = t2?[0]               // r8 = Option<Int64>.None
func test3() {
    t1?[0] = 10               // error: 't1?[0]' is immutable
    t2?[1] = 20               // error: 't2?[0]' is immutable
}

自增自减表达式

自增自减表达式是包含自增操作符(++)或自减操作符(--)的表达式;a++a-- 分别是 a+=1a-=1 的语法糖。自增和自减操作符实现对值的加 1 和减 1 操作,且只能作为后缀操作符使用。++--是非结合的,所以类似于a++--这样的在同一个表达式中同时包含两个及以上 ++/--但未使用圆括号规定计算顺序的表达式是被(从语法上)禁止的。

自增(自减)表达式的语法定义为:

incAndDecExpression
    : postfixExpression ('++' | '--' )
    ;

对于表达式 expr++(或 expr--),规定如下:

  1. expr 的类型必须是整数类型;

  2. 因为 expr++(或 expr--)是 expr += 1 (或 expr -= 1)的语法糖,所以此 expr 同时必须也是可被赋值的(见赋值表达式);

  3. expr++(或 expr--)的类型为 Unit

自增(自减)表达式举例:

var i: Int32 = 5
i++     // i = 6
i--     // i = 5
i--++   // syntax error
var j = 0
j = i-- // semantics error

算术表达式

算术表达式是包含算术操作符的表达式。仓颉编程语言支持的算术操作符包括:一元负号(-)、加(+)、减(-)、乘(*)、除(/)、取余(%)、求幂(**)。除了一元负号是一元前缀操作符,其他操作符均是二元中缀操作符。它们的优先级和结合性参见下文。

算术表达式的语法定义为:

prefixUnaryExpression
    : prefixUnaryOperator* incAndDecExpression
    ;
    
prefixUnaryOperator
    : '-'
    | ...
    ;

additiveExpression
    : multiplicativeExpression (additiveOperator multiplicativeExpression)*
    ;

multiplicativeExpression
    : exponentExpression (multiplicativeOperator exponentExpression)*
    ;

exponentExpression
    : prefixUnaryExpression (exponentOperator prefixUnaryExpression)*
    ;
    
additiveOperator
    : '+' | '-' 
    ;

multiplicativeOperator
    : '*' | '/' | '%' 
    ;

exponentOperator
    : '**' 
    ;

一元负号(-)的操作数只能是数值类型的表达式。一元前缀负号表达式的值等于操作数取负的值,类型和操作数的类型相同:

let num1: Int64 = 8 
let num2 = -num1      // num2 = -8, with 'Int64' type
let num3 = -(-num1)   // num3 =  8, with 'Int64' type

对于二元操作符 */%+-,要求两个操作数的类型相同。其中 % 的操作数只支持整数类型,*/+- 的操作数可以是任意数值类型。

let a = 2 + 3       // add:      5
let b = 3 - 1       // sub:      2
let c = 3 * 4       // multi:   12
let d = 6.6 / 1.1   // division: 6
let e = 4 % 3       // mod:      1

特别地,除法(/)的操作数为整数时,将非整数值向 0 的方向舍入为整数。整数取余运算 a % b 的值定义为 a - b * (a / b)

let q1 =  7 /  3    // integer division:   2
let q2 = -7 /  3    // integer division:  -2
let q3 =  7 / -3    // integer division:  -2
let q4 = -7 / -3    // integer division:   2
let r1 =  7 %  3    // integer remainder:  1
let r2 = -7 %  3    // integer remainder: -1
let r3 =  7 % -3    // integer remainder:  1
let r4 = -7 % -3    // integer remainder: -1

** 表示求幂运算(如 x**y 表示计算底数 x 的 y 次幂)。** 的左操作数只能为 Int64 类型或 Float64 类型,并且:

  1. 当左操作类型为 Int64 时,右操作数只能为 UInt64 类型,表达式的类型为 Int64

  2. 当左操作类型为 Float64 时,右操作数只能为 Int64 类型或 Float64 类型,表达式的类型为 Float64

let p1 = 2 ** 3               // p1 = 8
let p2 = 2 ** UInt64(3 ** 2)  // p2 = 512
let p3 = 2.0 ** 3.0           // p3 = 8.0
let p4 = 2.0 ** 3 ** 2        // p4 = 512.0
let p5 = 2.0 ** 3.0           // p5 = 8.0
let p6 = 2.0 ** 3.0 ** 2.0    // p6 = 512.0

当左操作类型为 Float64,右操作数类型为 Int64 时,存在一些特殊情况需要明确求幂表达式的值,具体罗列为:

x ** 0 = 1.0                               // for any x
0.0 ** n = POSITIVE_INFINITY               // for odd n < 0
-0.0 ** n = NEGATIVE_INFINITY              // for odd n < 0
0.0 ** n = POSITIVE_INFINITY               // for even n < 0
-0.0 ** n = POSITIVE_INFINITY              // for even n < 0
0.0 ** n = 0.0                             // for even n > 0
-0.0 ** n = 0.0                            // for even n > 0
0.0 ** n = 0.0                             // for odd n > 0
-0.0 ** n = -0.0                           // for odd n > 0
POSITIVE_INFINITY ** n = POSITIVE_INFINITY // for n > 0
NEGATIVE_INFINITY ** n = NEGATIVE_INFINITY // for odd n > 0
NEGATIVE_INFINITY ** n = POSITIVE_INFINITY // for even n > 0
POSITIVE_INFINITY ** n = 0.0               // for n < 0
NEGATIVE_INFINITY ** n = -0.0              // for odd n < 0
NEGATIVE_INFINITY ** n = 0.0               // for even n < 0.

注:在上述所列特殊情况之外,当左操作数的值为 NaN 时,无论右操作数取何值,求幂表达式的值均等于 NaN

当左操作类型为 Float64,右操作数类型为 Float64 时,同样存在一些特殊情况需要明确求幂表达式的值。具体罗列为:

x ** 0.0 = 1.0                              // for any x
x ** -0.0 = 1.0                             // for any x
0.0 ** y = POSITIVE_INFINITY                // for the value of y is equal to an odd integer < 0 
-0.0 ** y = NEGATIVE_INFINITY               // for the value of y is equal to an odd integer < 0 
0.0 ** NEGATIVE_INFINITY = POSITIVE_INFINITY 
-0.0 ** NEGATIVE_INFINITY = POSITIVE_INFINITY 
0.0 ** POSITIVE_INFINITY = 0.0 
-0.0 ** POSITIVE_INFINITY = 0.0 
0.0 ** y = 0.0                             // for finite y > 0.0 and its value is equal to an odd integer 
-0.0 ** y = -0.0                           // for finite y > 0.0 and its value is equal to an odd integer 
-1.0 ** POSITIVE_INFINITY = 1.0
-1.0 ** NEGATIVE_INFINITY = 1.0
1.0 ** y = 1.0                             // for any y 
x ** POSITIVE_INFINITY = 0.0               //  for -1.0 < x < 1.0
x ** POSITIVE_INFINITY = POSITIVE_INFINITY // for any x < -1.0 or for any x > 1.0
x ** NEGATIVE_INFINITY = POSITIVE_INFINITY // for -1.0 < x < 1.0 
x ** NEGATIVE_INFINITY = 0.0               // for any x < -1.0 or for any x > 1.0 
POSITIVE_INFINITY ** y = 0.0               // for y < 0.0 
POSITIVE_INFINITY ** y = POSITIVE_INFINITY // for y > 0.0 
NEGATIVE_INFINITY ** y = -0.0              // for finite y < 0.0 and its value is equal to an odd integer 
NEGATIVE_INFINITY ** y = NEGATIVE_INFINITY // for finite y > 0.0 and its value is equal to an odd integer 
NEGATIVE_INFINITY ** y = 0.0               // for finite y < 0.0 and its value is not equal to an odd integer 
NEGATIVE_INFINITY ** y = POSITIVE_INFINITY // for finite y > 0.0 and its value is not equal to an odd integer
0.0 ** y = POSITIVE_INFINITY               // for finite y < 0.0 and its value is not equal to an odd integer 
-0.0 ** y = POSITIVE_INFINITY              // for finite y < 0.0 and its value is not equal to an odd integer 
0.0 ** y = 0.0                             // for finite y > 0.0 and its value is not equal to an odd integer
-0.0 ** y = 0.0                            // for finite y > 0.0 and its value is not equal to an odd integer
x ** y = NaN                               // for finite x < 0.0 and finite y whose value is not equal to an integer

注:在上述所列特殊情况之外,一旦有操作数的值为 NaN,则求幂表达式的值等于 NaN

算术表达式结果为整数类型时存在整数溢出的可能。仓颉提供了三种属性宏及编译选项来控制整数溢出的处理行为(以下简称为行为)。如下表格所示:

attributesOptionsbehaviorexplaining for behavior
@OverflowThrowing--int-overflow throwingthrowingthrowing an exception
@OverflowWrapping--int-overflow wrappingwrappingwrapping around at the numeric bounds of the type
@OverflowSaturating--int-overflow saturatingsaturatingsaturating at the numeric bounds of the type

注:1. 默认的行为为 throwing。2. 假设属性宏与编译选项同时作用在某一范围,且代表的行为不相同,则该范围以属性宏代表的行为为准。

行为示例:

@OverflowThrowing
func test1(x: Int8, y: Int8) { // if x equals to 127 and y equals to 3
    let z = x + y // throwing OverflowException
}

@OverflowWrapping
func test2(x: Int8, y: Int8) { // if x equals to 127 and y equals to 3
    let z = x + y // z equals to -126
}

@OverflowSaturating
func test3(x: Int8, y: Int8) { // if x equals to 127 and y equals to 3
    let z = x + y // z equals to 127
}

属性宏与编译选项作用范围冲突示例:

// Compile with cjc --int-overflow saturating test.cj
// this file's name is test.cj

@OverflowWrapping
func test2(x: Int8, y: Int8) { 
    let z = x + y // the behavior is wrapping
}

func test3(x: Int8, y: Int8) { 
    let z = x + y // the behavior is saturating
}

特别地,对于 INT_MIN * -1, INT_MIN / -1INT_MIN % -1,规定的行为如下:

ExpressionThrowingWrappingSaturating
INT_MIN * -1 or -1 * INT_MINthrowing OverflowExceptionINT_MININT_MAX
INT_MIN / -1throwing OverflowExceptionINT_MININT_MAX
INT_MIN % -1000

需要注意的是,对于整数溢出行为是 throwing 的场景,若整数溢出可提前在编译期检测出来,则编译器会直接给出报错。

关系表达式

关系表达式是包含关系操作符的表达式。关系操作符包括6种:相等(==)、不等(!=)、小于(<)、小于等于(<=)、大于(>)、大于等于(>=)。关系操作符都是二元操作符,并且要求两个操作数的类型是一样的。关系表达式的类型是Bool类型,即值只可能是truefalse。关系操作符的优先级和结合性见下文。

关系表达式的语法定义为:

equalityComparisonExpression
    : comparisonOrTypeExpression (equalityOperator comparisonOrTypeExpression)?
    ;

comparisonOrTypeExpression
    : shiftingExpression (comparisonOperator shiftingExpression)?
    | ...
    ;
    
equalityOperator
    : '!=' | '=='
    ;
    
comparisonOperator
    : '<' | '>' | '<=' | '>='
    ;

关系表达式举例:

main(): Int64 {
    3 < 4         // return true
    3 <= 3        // return true
    3 > 4         // return false
    3 >= 3        // return true
    3.14 == 3.15  // return false
    3.14 != 3.15  // return true
    return 0
}

需要注意的是,关系运算符是非结合(non-associative)运算符,即无法写出类似于a < b < c这样的表达式。

main(): Int64 {
    3 < 4 < 5      // error: `<` is non-associative 
    3 == 3 != 4    // error: `==` and `!=` are non-associative 
    return 0
}

type test 和 type cast 表达式

type test 表达式是包含操作符 is 的表达式,type cast 表达式是包含操作符 as 的表达式。isas 的优先级和结合性参见下文。

type test 和 type cast 表达式的语法定义为:

comparisonOrTypeExpression
    : ...
    | shiftingExpression ('is' type)?
    | shiftingExpression ('as' userType)?
    ;

is 操作符

e is T 是一个用于类型检查的表达式,e is T 的类型是 Bool。其中 e 可以是任何类型的表达式,T 可以是任何类型。

当 e 的运行时类型 R 是 T 的子类型时,e is T 的值为 true,否则值为 false

is 操作符举例:

open class Base {
    var name: String = "Alice"
}
class Derived1 <: Base {
    var age: UInt8 = 18
}
class Derived2 <: Base {
    var gender: String = "female"
}

main(): Int64 {
    
    var testVT = 1 is Int64            // testVT = true
    testVT = 1 is String               // testVT = false
    testVT = true is Int64             // testVT = false
    testVT = [1, 2, 3] is Array<Int64> // testVT = true

    let base1: Base = Base()
    let base2: Base = Derived1()
    let base3: Base = Derived2()

    let derived1: Derived1 = Derived1()
    let derived2: Derived2 = Derived2()

    var test = base1 is Base          // test = true
    test = base1 is Derived1          // test = false
    test = base1 is Derived2          // test = false
    test = base2 is Base              // test = true
    test = base2 is Derived1          // test = true
    test = base2 is Derived2          // test = false
    test = base3 is Base              // test = true
    test = base3 is Derived1          // test = false
    test = base3 is Derived2          // test = true

    test = derived1 is Base           // test = true
    test = derived1 is Derived1       // test = true
    test = derived1 is Derived2       // test = false
    test = derived2 is Base           // test = true
    test = derived2 is Derived1       // test = false
    test = derived2 is Derived2       // test = true

    return 0
}

as 操作符

e as T 是一个用于类型转换的表达式,e as T 的类型是 Option<T>。其中 e 可以是任何类型的表达式,T 可以是任何具体类型。

当 e 的运行时类型 R 是 T 的子类型时,e as T 的值为 Some(e),否则值为 None

as 操作符举例:

open class Base {
    var name: String = "Alice"
}
class Derived1 <: Base {
    var age: UInt8 = 18
}
class Derived2 <: Base {
    var gender: String = "female"
}

main(): Int64 {
    let base1: Base = Base()
    let base2: Base = Derived1()
    let base3: Base = Derived2()

    let derived1: Derived1 = Derived1()
    let derived2: Derived2 = Derived2()
    
    let castOP1 = base1 as Base			// castOP = Option<Base>.Some(base1)
    let castOP2 = base1 as Derived1		// castOP = Option<Derived1>.None
    let castOP3 = base1 as Derived2		// castOP = Option<Derived2>.None
    let castOP4 = base2 as Base			// castOP = Option<Base>.Some(base2)
    let castOP5 = base2 as Derived1		// castOP = Option<Derived1>.Some(base2)
    let castOP6 = base2 as Derived2		// castOP = Option<Derived2>.None
    let castOP7 = base3 as Base			// castOP = Option<Base>.Some(base3)
    let castOP8 = base3 as Derived1		// castOP = Option<Derived1>.None
    let castOP9 = base3 as Derived2		// castOP = Option<Derived2>.Some(base3)

    let castOP10 = derived1 as Base		// castOP = Option<Base>.Some(derived1)
    let castOP11 = derived1 as Derived1	// castOP = Option<Derived1>.Some(derived1)
    let castOP12 = derived1 as Derived2	// castOP = Option<Derived2>.None
    let castOP13 = derived2 as Base		// castOP = Option<Base>.Some(derived2)
    let castOP14 = derived2 as Derived1	// castOP = Option<Derived1>.None
    let castOP15 = derived2 as Derived2	// castOP = Option<Derived2>.Some(derived2)

    return 0
}

位运算表达式

位运算表达式是包含位运算操作符的表达式。仓颉编程语言支持 1 种一元前缀位运算操作符:按位求反(!),以及 5 种二元中缀位运算操作符:左移(<<)、右移(>>)、按位与(&)、按位异或(^)和按位或(|)。位运算操作符的操作数只能为整数类型,通过将操作数视为二进制序列,然后在每一位上进行逻辑运算(0 视为 false1 视为 true )或移位操作来实现位运算。&^|的操作中,位与位之间执行的是逻辑操作(参见 [逻辑表达式])。位运算操作符的优先级和结合性参见下文。

位运算表达式的语法定义为:

prefixUnaryExpression
    : prefixUnaryOperator* incAndDecExpression
    ;
    
prefixUnaryOperator
    : '!'
    | ...
    ;

bitwiseDisjunctionExpression
    : bitwiseXorExpression ( '|' bitwiseXorExpression)*
    ;

bitwiseXorExpression
    : bitwiseConjunctionExpression ( '^' bitwiseConjunctionExpression)*
    ;

bitwiseConjunctionExpression
    : equalityComparisonExpression ( '&' equalityComparisonExpression)*
    ;
    
shiftingExpression
    : additiveExpression (shiftingOperator additiveExpression)*
    ;

shiftingOperator
    : '<<' | '>>'
    ;

位运算表达式举例:

func foo(): Unit {
    !10             // The result is -11
    !20             // The result is -21
    10 << 1         // The result is 20
    10 << 1 << 1    // The result is 40
    10 >> 1         // The result is 5
    10 & 15         // The result is 10
    10 ^ 15         // The result is 5
    10 | 15         // The result is 15
    1 ^ 8 & 15 | 24 // The result is 25
}

对于移位操作符,要求其操作数必须是整数类型(但两个操作数的类型可以不一样),并且无论左移还是右移,右操作数都不允许为负数(对于编译时可检查出的此类错误,编译报错,如果运行时发生此错误,则抛出异常)。

对于无符号数的移位操作,移位和补齐规则是:左移低位补 0 高位丢弃,右移高位补 0 低位丢弃。对于有符号数的移位操作,移位和补齐规则是:

  1. 正数和无符号数的移位补齐规则一致;
  2. 负数左移低位补 0 高位丢弃;
  3. 负数右移高位补 1 低位丢弃。
let p: Int8 = -30   
let q = p << 2      // q = -120
let r = p >> 2      // r = -8 
let r = p >> -2     // error
let x: UInt8 = 30 
let b = x << 3      // b = 240
let b = x >> 1      // b = 15

另外,如果右移或左移的位数(右操作数)等于或者大于操作数的宽度,则为overshift,如果编译时可以检测到则报错,否则运行时抛出异常。

let x1 : UInt8 = 30 // 0b00011110
let y1 = x1 >> 11   // compilation error

区间表达式

区间表达式是包含区间操作符的表达式。区间表达式用于创建 Range 实例。区间表达式的语法定义为:

rangeExpression
    : bitwiseDisjunctionExpression ('..=' | '..') bitwiseDisjunctionExpression (':' bitwiseDisjunctionExpression)?
    | bitwiseDisjunctionExpression
    ;

区间操作符有两种:....=,分别用于创建“左闭右开”和“左闭右闭”的 Range 实例。关于它们的介绍,请参见 [Range 类型]。

逻辑表达式

逻辑表达式是包含逻辑操作符的表达式。逻辑操作符的操作数只能为Bool类型的表达式。仓颉编程语言支持 4 种逻辑操作符:逻辑非(!)、逻辑与(&&)、逻辑或(||)。它们的优先级和结合性参见下文。

逻辑表达式的语法定义为:

prefixUnaryExpression
    : prefixUnaryOperator* incAndDecExpression
    ;
    
prefixUnaryOperator
    : '!'
    | ...
    ;

logicDisjunctionExpression
    : logicConjunctionExpression ( '||' logicConjunctionExpression)*
    ;

logicConjunctionExpression
    : rangeExpression ( '&&' rangeExpression)*
    ;

**逻辑非(!)**是一元操作符,它的作用是对其操作数的布尔值取反:!false 的值等于 true,!true 的值等于 false。

**逻辑与(&&)和逻辑或(||)**均是二元操作符。对于表达式 expr1 && expr2,只有当 expr1expr2 的值均等于 true 时,它的值才等于 true;对于表达式 expr1 || expr2,只有当 expr1expr2 的值均等于 false 时,它的值才等于 false

&&|| 采用短路求值策略:计算 expr1 && expr2 时,当 expr1=false 则无需对 expr2求值,整个表达式的值为 false;计算 expr1 || expr2 时,当 expr1=true 则无需对 expr2求值,整个表达式的值为 true

main(): Int64 {
    let expr1 = false
    let expr2 = true
    !true                           // Logical NOT, return false.
    1 > 2 && expr1                  // Logical AND, return false without computing the value of expr1.
    1 < 2 || expr2                  // Logical OR, return true without computing the value of expr2.
    return 0
}

coalescing 表达式

coalescing 表达式是包含 coalescing 操作符的表达式。coalescing 操作符使用 ?? 表示,?? 是二元中缀操作符,其优先级和结合性参见下文。

coalescing 表达式的语法定义为:

coalescingExpression
    : logicDisjunctionExpression ('??' logicDisjunctionExpression)*
    ;

coalescing 操作符用于 Option 类型的解构。假设表达式 e1 的类型是 Option<T>,对于表达式 e1 ?? e2,规定:

  1. 表达式 e2 具有类型 T
  2. 表达式 e1 ?? e2 具有类型 T
  3. e1 的值等于 Option<T>.Some(v) 时,e1 ?? e2 的值等于 v 的值(此时,不会再去对 e2 求值,即满足“短路求值”);当 e1 的值等于 Option<T>.None 时,e1 ?? e2 的值等于 e2 的值。

表达式 e1 ?? e2 是如下 match 表达式的语法糖:

// when e1 is Option<T>
match (e1) {
    case Some(v) => v
    case None => e2
}

coalescing 表达式使用举例:

main(): Int64 {
    let v1 = Option<Int64>.Some(100)
    let v2 = Option<Int64>.None
    let r1 = v1 ?? 0
    let r2 = v2 ?? 0
    print("${r1}") // output: 100
    print("${r2}") // output: 0
    
    return 0
}

流表达式

流表达式是包含流操作符的表达式。流操作符包括两种:表示数据流向的中缀操作符|>(称为pipeline)和表示函数组合的中缀操作符~>(称为composition)。|>~>的优先级相同,并介于 || 和赋值操作符=之间。|>~>的结合性均为左结合,详情参考下文。流表达式的语法定义为:

flowExpression
    : logicDisjunctionExpression (flowOperator logicDisjunctionExpression)*
    ;

flowOperator
    : '|>' | '~>'
    ;

pipeline操作符

pipeline 表达式是单个参数函数调用的语法糖,即 e1 |> e2let v = e1; e2(v) 的语法糖(即先对 |> 操作符左边的 e1 求值)。这里 e2 是函数类型的表达式,e1 的类型是 e2 的参数类型的子类型;或者 e2 的类型重载了函数调用操作符 ()(参见 [可以被重载的操作符])。

注意:这里的 f 不能是 initsuper 构造函数。

func f(x: Int32): Int32 { x + 1 }

let a: Int32 = 1
var res = a |> f // ok
var res1 = a |> {x: Int32 => x + 1} // ok

func h(b: Bool) { b }
let res3 = a < 0 || a > 10  |> h // Equivalence:(a < 0 || a > 10)  |> h

func g<T>(x: T): T { x }
var res4 = a |> g<Int32> // ok

class A {
    let a: Int32
    let b: Int32
    init(x: Int32) {
        a = x
        b = 0
    }
    init(x: Int32, y: Int32) {
        x |> init // error: `init` is not a valid expression
        b = y
    }
}

// PIPELINE with operator `()` overloading
class A {
    operator func ()(x: Int32) {
        x
    }
}
let obj = A()
let a: Int32 = 1
let res = a |> obj // Equivalence:obj(a)

composition操作符

composition 表达式表示两个单参函数的组合。也就是说,composition 表达式 e1 ~> e2let f = e1; let g = e2; {x => g(f(x))} 的语法糖(即先对 ~> 操作符左边的 e1 求值)。这里的 fg 均为函数类型的表达式或者其类型重载了单参的函数调用操作符 ()(参见 [可以被重载的操作符]),则会有以下四种情况:

e1 ~> e2The lambda expression
e1 and e2 are function types, and the return value type of e1 is a subtype of the argument type of e2let f = e1; let g = e2; {x => g(f(x))}
The type f implements the single-parameter operator () overloading function, and g is a function type, and the return value type of f.operator() is a subtype of the argument type of glet f = e1; let g = e2; {x => g(f.operator()(x))}
f is a function type, and the type of g implements the the single-parameter operator () overloading function, and the return value type of f is a subtype of the argument type of g.operator().let f = e1; let g = e2; {x => g.operator()(f(x))}
The types of f and g both implement the single-parameter operator () overloading function, and the return value type of f.operator() is a subtype of the argument type of g.operator()let f = e1; let g = e2; {x => g.operator()(f.operator()(x))}

注意:这里的 e1e2 求值后不能是 initsuper 构造函数。

func f(x: Int32): Float32 { Float32(x) }
func g(x: Float32): Int32 { Int32(x) }

var fg = f ~> g // Equivalence: {x: Int32 => g(f(x))}

let lambdaComp = {x: Int32 => x} ~> f // ok

func h1<T>(x: T): T { x }
func h2<T>(x: T): T { x }
var hh = h1<Int32> ~> h2<Int32> // ok

// COMPOSITION with operator `()` overloading
class A {
    operator func ()(x: Int32): Int32 {
        x
    }
}
class B {
    operator func ()(x: Float32): Float32 {
        x
    }
}
let objA = A()
let objB = B()
let af = objA ~> f // ok
let fb = f ~> objB // ok
let aa = objA ~> objA // ok

赋值表达式

赋值表达式是包含赋值操作符的表达式,用于将左操作数的值修改为右操作数的值,要求右操作数的类型是左操作数类型的子类型。对赋值表达式求值时,总是先计算 = 右边的表达式,再计算 = 左边的表达式,最后进行赋值。

对于复合赋值表达式求值时,总是先计算 = 左边的表达式的左值,然后根据这个左值取右值,然后将该右值与 = 右边的表达式做计算(若有短路规则会继续遵循短路规则),最后赋值。

除了子类型允许的赋值外,如果右操作数是字符串字面量,而左操作数的类型是 ByteRune,则字符串值将分别被强制赋值为 ByteRune,并对强制赋值进行赋值。

赋值操作符分为普通赋值操作符和复合赋值操作符,赋值表达式的语法定义为:

assignmentExpression
    : leftValueExpressionWithoutWildCard assignmentOperator flowExpression
    | leftValueExpression '=' flowExpression
    | tupleLeftValueExpression `=` flowExpression
    | flowExpression
    ;

tupleLeftValueExpression
    : `(` (leftValueExpression | tupleLeftValueExpression) (`,` (leftValueExpression | tupleLeftValueExpression))+ `,`? `)`
    ;

leftValueExpression
    : leftValueExpressionWithoutWildCard
    | '_'
    ;

leftValueExpressionWithoutWildCard
    : identifier
    | leftAuxExpression '?'? assignableSuffix
    ;

leftAuxExpression   
    : identifier typeArguments?
    | type
    | thisSuperExpression
    | leftAuxExpression ('?')? '.' identifier typeArguments?
    | leftAuxExpression ('?')? callSuffix
    | leftAuxExpression ('?')? indexAccess
    ;

assignableSuffix
    : fieldAccess
    | indexAccess
    ;

fieldAccess
    : '.' identifier
    ;

assignmentOperator
    : '=' | '+=' | '-=' | '**=' | '*=' | '/=' | '%=' | '&&=' | '||=' 
    | '&=' | '|=' | '^=' | '<<=' | '>>='
    ;

出现在(复合)赋值操作符左侧的表达式称为左值表达式(上述定义中的 leftValueExpression)。

语法上,左值表达式可以是一个 identifier_,或者一个 leftAuxExpression 后接 assignableSuffix(包含 fieldAccessindexAccess 两类),leftAuxExpressionassignableSuffix 之间可以有可选的 ? 操作符(对 Option Type 的实例进行赋值的语法糖)。leftAuxExpression 可以是以下语法形式:1、一个包含可选类型实参(typeArguments)的 identifier;2、thissuper;3、一个 leftAuxExpression 后接一个 .(二者之间可以有可选的 ? 操作符)和一个存在可选类型实参的 identifier;4、一个 leftAuxExpression 后接一个函数调用后缀 callSuffix 或索引访问后缀 indexAccesscallSuffixindexAccess 之前可以有可选的 ? 操作符)。

语义上,左值表达式只能是如下形式的表达式:

  1. identifier 表示的变量(参见 [变量名和函数名]);

  2. 通配符 _,意味着忽略 = 右侧表达式的求值结果(复合赋值表达式禁止使用通配符);

  3. 成员访问表达式 e1.a 或者 e2?.a(参见 [成员访问表达式]);

  4. 索引访问表达式 e1[a] 或者 e2?[a](参见 [索引访问表达式])。

注:其中 e1e2 必须是满足 leftAuxExpression 语法的表达式。

左值表达式是否合法,取决于左值表达式是否是可变的:仅当左值表达式可变时,它才是合法的。关于上述表达式的可变性,可参见对应章节。

赋值表达式的类型是 Unit,值是(),这样做的好处是可以避免类似于错误地将赋值表达式当做判等表达式使用等问题的发生。在下面的例子中,如果先执行(a = b),则返回值是(),而 () 不能出现在 = 的左侧,所以执行()=0时就会报错。同样地,由于if之后的表达式必须为Bool类型,所以下例中的if表达式也会报错。另外,=是非结合的,所以类似于a = b = 0这样的同一个表达式中同时包含两个以上=的表达式是被禁止的(无法通过语法检查)。

main(): Int64 {
    var a = 1
    var b = 1
    a = (b = 0) // semantics error
    if (a = 5) {  // semantics error
    }
    a = b = 0   // syntax error
    
    return 0
}

复合赋值表达式 a op= b 不能简单看做赋值表达式与其他二元操作符的组合 a = a op b(其中op可以是算术操作符、逻辑操作符和位操作符中的任意二元操作符,操作数ab的类型为操作符op所要求的类型)。在仓颉语言中,a op= b 中的 a 只会被求值一次(副作用也只发生一次),而 a = a op b 中的 a 会被求值两次(副作用也发生两次)。因为复合赋值表达式也是一个赋值表达式,所以复合赋值操作符也是非结合的。复合赋值表达式同样要求两个操作数的类型相同。

下面举例说明复合赋值表达式的使用:

a **= b
a *= b 
a /= b
a %= b 
a += b 
a -= b 
a <<= b
a >>= b
a &&= b
a ||= b
a &= b 
a ^= b 
a |= b 

最后,如果用户重载了 ***/%+-<<>>&^| 操作符,那么仓颉语言会提供其对应的复合赋值操作符 **=*=/=%=+=-=<<=>>=&=^=|= 的默认实现。但有些额外的要求,否则无法为 a = a op b 提供赋值语义:

  1. 重载后的操作符的返回类型需要与左操作数的类型一致或是其子类型,即对于 a op= b 中的 abop,它们需要能通过 a = a op b 的类型检查。例如当有子类型关系 A <: B <: C 时,若用户重载的 + 的类型是 (B, Int64) -> B(B, Int64) -> A,则仓颉语言可以提供默认实现;若用户重载的 + 的类型是 (B, Int64) -> C,则仓颉语言不会为其提供默认实现。
  2. 要求 a op= b 中的 a 必须是可被赋值的,例如是一个变量。

多赋值表达式是一种特殊的赋值表达式,多赋值表达式等号左边必须是一个 tuple,这个 tuple 里面的元素必须都是左值,等号右边的表达式也必须是 tuple 类型,右边 tuple 每个元素的类型必须是对应位置左值类型的子类型。注意:当左侧 tuple 中出现 _ 时,表示忽略等号右侧 tuple 对应位置处的求值结果(意味着这个位置处的类型检查总是可以通过的)。

多赋值表达式可以将右边的 tuple 类型的值,一次性赋值给左边 tuple 内的对应左值,省去逐个赋值的代码。

main(): Int64 {
    var a: Int64
    var b: Int64

    (a, b) = (1, 2) // a == 1, b == 2
    (a, b) = (b, a) // swap, a == 2, b == 1
    (a, _) = (3, 4) // a == 3
    (_, _) = (5, 6) // no assignment

    return 0
}

多赋值表达式可以看成是如下形式的语法糖。赋值表达式右侧的表达式会优先求值,再对左值部分从左往右逐个赋值。

main(): Int64 {
    var a: Int64
    var b: Int64
    (a, b) = (1, 2)
    // desugar
    let temp = (1, 2)
    a = temp[0]
    b = temp[1]
    return 0
}

Lambda 表达式

Lambda 表达式是函数类型的值,详见第 5 章函数。

Quote 表达式

Quote 表达式用于引用代码,并将其表示为可操作的数据对象,主要用于元编程,详见第 14 章元编程。

宏调用表达式

宏调用表达式用于调用仓颉定义的宏,主要用于元编程,详见第 14 章元编程。

引用传值表达式

引用传值表达式只可用于 C 互操作中调用 CFunc 场景中,详见第 13 章互操作中 inout 参数一节。

操作符的优先级和结合性

对于包含两个或两个以上操作符的表达式,它的值由操作符和操作数的分组结合方式决定,而分组结合方式取决于操作符的优先级和结合性。简单来讲,优先级规定了不同操作符的求值顺序,结合性规定了具有相同优先级的操作符的求值顺序。

如果一个表达式中包含多个不同优先级的操作符,那么它的计算顺序是:先计算包含高优先级操作符的子表达式,再计算包含低优先级操作符的子表达式。在包含多个同一优先级操作符的子表达式中,计算次序由操作符的结合性决定。

下表列出了各操作符的优先级、结合性、功能描述、用法以及表达式的类型。其中越靠近表格顶部,操作符的优先级越高:

OperatorAssociativityDescriptionUsageExpression type
@Right associativemacro call expression@expr1 @expr2Unit
.Left associativeMember accessName.nameThe type of name
[]Index accessvarName[expr]The type of the element of varName
()Function callfuncName(expr)Return type of funcName
++NonePostfix incrementvarName++Unit
--Postfix decrementvarName--Unit
?Question markexpr1?.expr2 etc.Option<T> (T is the type of expr2)
!Right associativeBitwise Logic NOT!exprThe type of expr
-Unary negative-expr
**Right associativePowerexpr1 ** expr2The type of expr1
*Left associativeMultiplyexpr1 * expr2The type of expr1 or expr2, since expr1 and expr2 have the same type
/Divideexpr1 / expr2
%Remainderexpr1 % expr2
+Left associativeAddexpr1 + expr2The type of expr1 or expr2, since expr1 and expr2 have the same type
-Subtractexpr1 - expr2
<<Left associativeBitwise left shiftexpr1 << expr2The type of expr1, where expr1 and expr2 can have different types
>>Bitwise right shiftexpr1 >> expr2
..NoneRange operatorexpr1..expr2:expr3Range type
..=expr1..=expr2:expr3
<NoneLess thanexpr1 < expr2Except the type of 'expr as userType' is Option<userType>, other expressions have Bool type
<=Less than or equalexpr1 <= expr2
>Greater thanexpr1 > expr2
>=Greater than or equalexpr1 >= expr2
isType checkexpr is type
asType castexpr as userType
==NoneEqualexpr1 == expr2Bool
!=Not equalexpr1 != expr2
&Left associativeBitwise ANDexpr1 & expr2The type of expr1 or expr2, since expr1 and expr2 have the same type
^Left associativeBitwise XORexpr1 ^ expr2The type of expr1 or expr2, since expr1 and expr2 have the same type
``Left associativeBitwise OR`expr1
&&Left associativeLogic ANDexpr1 && expr2Bool
``Left associativeLogic OR
??Right associativecoalescingexpr1 ?? expr2The type of expr2
`>`Left associativePipeline`expr1
~>Compositionexpr1 ~> expr2The type of expr1 ~> expr2 is the type of the lambda expression {x=>expr2(expr1(x))}
=NoneAssignmentleftValue = exprUnit
**=Compound assignmentleftValue **= expr
*=leftValue *= expr
/=leftValue /= expr
%=leftValue %= expr
+=leftValue += expr
-=leftValue -= expr
<<=leftValue <<= expr
>>=leftValue >>= expr
&=leftValue &= expr
^=leftValue ^= expr
`=``leftValue
&&=leftValue &&= expr
`=`

注:?.(){}[] 一起使用时,是一种语法糖形式,不会严格按照它们固有的优先级和结合性进行求值,详见问号操作符。

表达式的求值顺序

表达式的求值顺序规定了计算操作数的值的顺序,显然只有包含二元操作符的表达式才存在求值顺序的概念。仓颉编程语言的默认求值顺序为:

  1. 对于包含逻辑与(&&)、逻辑或(||)和 coalescing??)的表达式,仅当操作符的右操作数的值会影响整个表达式的值时,才计算右操作数的值,否则只计算左操作数的值。因此,&&||?? 的求值顺序为:先计算左操作数的值,再计算右操作数的值。

  2. 对于 optional chaining 表达式,其中的 ? 会将表达式分隔成若干子项,按从左到右的顺序对各子项依次求值(子项内按使用到的操作符的求值顺序进行求值)。

  3. 对于其他表达式(如算术表达式、关系表达式、位运算表达式等),同样按从左往右的顺序求值。