仓颉入门:特性
1、Flow 表达式
Flow 流表示对输入进行流式处理后得到输出,在仓颉中流操作符有两种:表示数据流向的中缀操作符 |> 和表示函数组合的中缀操作符 ~> 。
1.1、|>
表示对输入数据做一系列的处理,可以使用 |> 来简化。
语法形式就是 e1 |> e2,其中 e2 是函数类型的表达式,e1 的类型是 e2 的参数类型的子类型。
示例1:
let gen: Array<Int64> = []
gen |> forEach{a: Int64 => println("${a}")}示例2:
func split(words: Array<String>, separator: Rune): Array<String> {words |> map { text =>text.split(String(separator), removeEmpty: true)} |> flatten |> collectArray
}//示例3: 处理 Unicode 字符串
func split_unicode(text: String, sep: String) {let indices = ArrayList<Int64>()text.runes() |> enumerate |> fold(false) { state, e =>let current = !sep.contains(e[1].toString())if (state != current) { indices.append(e[0]) }current} |> { valid: Bool => if (valid) { indices.append(text.size) } }let runes = text.toRuneArray()let result = ArrayList<String>()for (i in 0..indices.size:2) {result.append(String(runes[indices[i]..indices[i + 1]]))}return result
}
1.2、~>
当两个单参函数进行组合时可以使用~>。
//示例:
func f(x: Int64): Float64 {Float64(x)
}
func g(x: Float64): Float64 {x
}
//等价于{ x: Int64 => g(f(x)) },会先对f求值,然后再对g求值,最后才会进行函数的组合
var fg = f ~> g
建议以上两个流操作符不要用于带默认值的命名参数,因为配置了默认值的命名参数必须给出命名实参才能用
2、变长参数
变长参数是各个语言都有的一种功能,但是在仓颉中当形参最后一个非命名参数是 Array 类型时,就可以作为变长参数使用,不需要特殊的语法形式
func sum(arr: Array<Int64>) {var total = 0for (x in arr) {total += x}return total
}main() {println(sum())println(sum(1, 2, 3))
}
3、扩展
仓颉扩展提供了对类和接口进行扩展的操作,不需要使用继承或者装饰器模式,就可以给类添加额外的属性和方法。这种场景的优势在于不需要破坏被扩展类型的封装性,就可以添加额外的功能。
可以添加的功能包括:
•添加成员函数
•添加操作符重载函数
•添加成员属性
•实现接口
//示例1:为整型扩展两个属性
extend Int64 {public prop r: Index {get() { Index.Row(this) }}public prop c: Index {get() { Index.Col(this) }}
}
//调用
2.r//示例2:扩展静态方法
extend Expression {static func fromTokens(tokens: List<Token>): Result<Expression, String> {match (expressionFunc(tokens).map {t => t[0]}) {case Some(e) => Ok(e)case None => Err("Invalid Expression!")}}
}
//调用
Expression.fromTokens
4、if-let 和 while-let
仓颉中的条件控制和循环控制基本和 ArkTs 一致,除了将 switch 换为 match 外无区别,但是多了 if-let 和 while-let 两个特性表达式。
4.1、if-let
if-let 表达式首先会对条件中 <- 右侧的表达式进行求值,如果此值能匹配 <- 左侧的模式,则执行 if 分支,否则执行 else 分支。
main() {let result = Option<Int64>.Some(2023)if (let Some(value) <- result) {println("操作成功,返回值为:${value}")} else {println("操作失败")}
}
操作成功,返回值为:2023
仓颉没有 null 判空,所以提供了 option(T)来判空,它里面的取值就是 some(v)和 none 两种情况 None 为空,表示没有赋值。some 表示有赋值
4.2、while-let
while-let 表达式同 if-let 一样,也是先会对条件中 <- 右侧的表达式进行求值,如果此值能匹配 <- 左侧的模式,则执行循环体,然后重复执行此过程。如果模式匹配失败,则结束循环。
public static func fromJson(r: JsonReader): FunctionCall {var temp_name: String = "";var temp_arguments: String = "";while (let Some(v) <- r.peek()) {match(v) {case BeginObject =>r.startObject();while(r.peek() != EndObject) {let n = r.readName()match (n) {case "name" => temp_name = r.readValue<String>();case "arguments" => temp_arguments = r.readValue<String>();case unkow => println("unkow key ${unkow}");}}r.endObject();break;case _ => throw Exception("can't deserialize for FunctionCall");}}return FunctionCall(temp_name, temp_arguments);}
5、线程
仓颉中创建一个线程非常简单,只需要使用 spawn{} 即可开启一个新的线程,{}里面就是在新线程中执行的代码,并且可以使用 Future获取线程执行结果。
import std.sync.*
import std.time.*//开启一个线程
let future = spawn { task() }//执行内容
func task(): Int64 {for (_ in 0..M) {.....}return n
}//使用fut.get()可以等待线程执行完成获取线程执行结果
future.get()
5.1、同步-原子操作
仓颉同样也提供了多种同步机制来确保数据的安全,例如原子操作、互斥锁和条件变量等
原子操作方面仓颉提供整数类型、AtomicBool 类型和 AtomicReference 引用类型的原子操作来保证同步操作。
let num = AtomicInt64(0);
let list = ArrayList<Future<Int64>>();
func testAtomic() {for (i in 0..100) {let fut = spawn {sleep(Duration.millisecond)num.fetchAdd(1)}list.append(fut)}for (f in list) {f.get()}let result = num.load()AppLog.info("result = ${result}")//输出100
}
5.2、同步-可重入互斥锁 ReentrantMutex
可重入互斥锁可以保证在任意时刻最多只有一个线程执行区块的代码,当一个线程尝试获取被其他线程持有的锁会被阻塞,直到别的线程释放锁才可以执行区块代码。使用 ReentrantMutex 需要注意以下两点:
1.在访问共享数据之前,必须尝试获取锁。
2.处理完共享数据后,必须进行解锁,以便其他线程可以获得锁。
import std.sync.*
import std.time.*
import std.collection.*var num: Int64 = 0;
let list = ArrayList<Future<Int64>>();
let lock = ReentrantMutex()func task() {sleep(Duration.millisecond)lock.lock()num++lock.unlock()
}func testMutex() {let list = ArrayList<Future<Unit>>()for (i in 0..100) {let fut = spawn {task()}list.append(fut)}for (f in list) {f.get()}AppLog.info("result = ${num}") //输出100
}
在日常使用中需要手动 unlock 相当不方便,而且也有可能在异常情况下锁无法释放的问题,为了解决这些问题,仓颉又提供一个 synchronized 关键字,搭配 ReentrantMutex 一起使用。
具体使用方式就是在 synchronized 上加一个 ReentrantMutex 对象即可,然后将同步代码写在 synchronized{}中。一个线程在进入 synchronized 修饰的代码块之前,会自动获取 ReentrantMutex 实例对应的锁,如果无法获取锁,则当前线程被阻塞;而线程在退出 synchronized 修饰的代码块之前,会自动释放该 ReentrantMutex 实例的锁。
import std.sync.*
import std.time.*
import std.collection.*var num: Int64 = 0;
let list = ArrayList<Future<Int64>>();
let lock = ReentrantMutex()func task() {sleep(Duration.millisecond)// lock.lock()//跟上面的示例一样,省去了加,释放锁的操作synchronized(lock) {num++}// lock.unlock()
}func testMutex() {let list = ArrayList<Future<Unit>>()for (i in 0..100) {let fut = spawn {task()}list.append(fut)}for (f in list) {f.get()}AppLog.info("result = ${num}") //输出100
}