composition 操作符

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

在这里插入图片描述

注意:这里的 e1,e2 求值后不能是 init 和 super 构造函数。

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

赋值表达式

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

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

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

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

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 后接 assignable-Suffix(包含
fieldAccess 和 indexAccess 两类),leftAuxExpression 和 assignableSuffix 之间可以有可选的 ? 操作符(对 Option Type 的实例进行赋值的语法糖)。leftAuxExpression 可以是以下语法形式:1、一个包含可选类型实参(typeArguments)的 identifier;2、this 或 super;3、一个 leftAuxEx-pression
后接一个 .(二者之间可以有可选的 ? 操作符)和一个存在可选类型实参的 identifier;4、一个 leftAuxExpression 后接一个函数调用后缀 callSuffix 或索引访问后缀 indexAccess(callSuffix或 indexAccess 之前可以有可选的 ? 操作符)。

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

  1. identifier 表示的变量(参见变量名和函数名);
  2. 通配符 _,意味着忽略 = 右侧表达式的求值结果(复合赋值表达式禁止使用通配符)

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

注:其中 e1 和 e2 必须是满足 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 可以是算术操作符、逻辑操作符和位操作符中的任意二元操作符,操作数 a 和 b 的类型为操作符 op 所要求的类型)。在仓颉语言中,a op= b 中的 a 只会被求值一次(副作用也只发生一次),而 a = a op b 中的a 会被求值两次(副作用也发生两次)。因为复合赋值表达式也是一个赋值表达式,所以复合赋值操作符也是非结合的。复合赋值表达式同样要求两个操作数的类型相同。

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

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

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

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

要求 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
}

操作符的优先级和结合性

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

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

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

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

表达式的求值顺序

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

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

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

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

Logo

新一代开源开发者平台 GitCode,通过集成代码托管服务、代码仓库以及可信赖的开源组件库,让开发者可以在云端进行代码托管和开发。旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐