Appearance
集合类型
Targo 提供了与 Go 语义一致的集合类型,让你能够高效地处理数据集合。本指南详细介绍 slice、map、chan 和 Fixed 数组的使用。
核心概念
Targo 的集合类型遵循以下设计原则:
- 小写类型名 = Go 原生类型(零开销):
slice<T>,map<K,V>,chan<T> - 大写类型名 = Runtime 类型(带方法):
Array<T>继承自slice<T> - 实例方法 用于查询和修改自身:
s.len(),m.delete(key) - 命名空间方法 用于构造和多参数操作:
slice.make(),slice.append()
slice<T> - 动态数组
slice 是 Targo 中最常用的集合类型,对应 Go 的切片。它是动态大小的数组视图,具有长度和容量。
创建 slice
typescript
// 使用数组字面量(最常用)
let numbers: slice<int> = [1, 2, 3, 4, 5];
// 使用 slice.of() 工厂方法
let colors = slice.of("red", "green", "blue");
// 使用 slice.make() 创建指定长度和容量
let buffer = slice.make<byte>(0, 1024); // 长度 0,容量 1024
let zeros = slice.make<int>(10); // 长度 10,容量 10,元素为 0
// 空 slice
let empty: slice<string> = [];
let nilSlice = zero<slice<int>>(); // nil sliceTypeScript 对比
在 TypeScript 中,你使用 Array<T> 或 T[]。在 Targo 中,使用 slice<T> 表示 Go 原生切片。
访问和修改
typescript
let nums = slice.of(10, 20, 30, 40, 50);
// 索引访问
let first = nums[0]; // 10
nums[1] = 25; // 修改元素
// 长度和容量
console.log(nums.len()); // 5
console.log(nums.cap()); // 5
// 切片操作
let sub = nums.slice(1, 4); // [20, 25, 40]
let tail = nums.slice(2); // [30, 40, 50]追加元素
typescript
let nums = slice.of(1, 2, 3);
// 追加单个元素
nums = slice.append(nums, 4);
// 追加多个元素
nums = slice.append(nums, 5, 6, 7);
// 追加另一个 slice
let more = slice.of(8, 9, 10);
nums = slice.append(nums, ...more);
console.log(nums); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]重要
slice.append() 返回新的 slice,必须重新赋值:nums = slice.append(nums, 4)
复制 slice
typescript
let src = slice.of(1, 2, 3, 4, 5);
let dst = slice.make<int>(5);
// 复制元素
let n = slice.copy(dst, src); // 返回复制的元素数量
console.log(n); // 5
console.log(dst); // [1, 2, 3, 4, 5]清空 slice
typescript
let nums = slice.of(1, 2, 3, 4, 5);
nums.clear();
console.log(nums.len()); // 0迭代 slice
typescript
let fruits = slice.of("apple", "banana", "cherry");
// for-of 循环(只获取值)
for (const fruit of fruits) {
console.log(fruit);
}
// for-of 循环(获取索引和值)
for (const [i, fruit] of fruits.entries()) {
console.log(`${i}: ${fruit}`);
}
// 传统 for 循环
for (let i = 0; i < fruits.len(); i++) {
console.log(fruits[i]);
}map<K, V> - 键值映射
map 是无序的键值对集合,对应 Go 的 map。
创建 map
typescript
// 使用 map.make() 创建空 map
let scores = map.make<string, int>();
// 使用 map.of() 创建并初始化
let ages = map.of<string, int>(
["Alice", 30],
["Bob", 25],
["Charlie", 35]
);操作 map
typescript
let scores = map.make<string, int>();
// 设置键值对
scores.set("Alice", 95);
scores.set("Bob", 87);
scores.set("Charlie", 92);
// 获取值(如果键不存在,返回零值)
let aliceScore = scores.get("Alice"); // 95
let davidScore = scores.get("David"); // 0(零值)
// 检查键是否存在
let [score, exists] = scores.getOK("Alice");
if (exists) {
console.log(`Alice's score: ${score}`);
}
// 删除键
scores.delete("Bob");
// 获取长度
console.log(scores.len()); // 2
// 清空 map
scores.clear();TypeScript 对比
TypeScript 的 Map 使用 .has() 检查键,Targo 使用 .getOK() 返回 [value, exists] 元组。
迭代 map
typescript
let scores = map.of<string, int>(
["Alice", 95],
["Bob", 87],
["Charlie", 92]
);
// 迭代键值对
for (const [name, score] of scores) {
console.log(`${name}: ${score}`);
}
// 只迭代键
for (const name of scores.keys()) {
console.log(name);
}
// 只迭代值
for (const score of scores.values()) {
console.log(score);
}注意
map 的迭代顺序是随机的,不保证顺序。
chan<T> - 通道
chan 是用于 goroutine 之间通信的管道,对应 Go 的 channel。
创建 channel
typescript
// 无缓冲 channel(同步)
let ch = chan.make<int>();
// 有缓冲 channel(异步)
let buffered = chan.make<string>(10); // 缓冲大小 10发送和接收
typescript
let ch = chan.make<int>(5);
// 发送值
ch.send(42);
ch.send(100);
// 接收值
let value = ch.receive(); // 42
console.log(value);
// 尝试接收(非阻塞)
let [val, ok] = ch.tryReceive();
if (ok) {
console.log(`Received: ${val}`);
} else {
console.log("Channel is empty or closed");
}关闭 channel
typescript
let ch = chan.make<int>(3);
ch.send(1);
ch.send(2);
ch.send(3);
// 关闭 channel
ch.close();
// 从已关闭的 channel 接收会返回零值
let [val, ok] = ch.tryReceive();
if (!ok) {
console.log("Channel is closed");
}迭代 channel
typescript
let ch = chan.make<string>(3);
// 在另一个 goroutine 中发送数据
go(() => {
ch.send("hello");
ch.send("world");
ch.send("!");
ch.close();
});
// 迭代直到 channel 关闭
for (const msg of ch) {
console.log(msg);
}使用 select 多路复用
typescript
let ch1 = chan.make<int>();
let ch2 = chan.make<string>();
switch (chan.$select) {
case ch1.$recv:
let num = ch1.$value;
console.log(`Received number: ${num}`);
break;
case ch2.$recv:
let str = ch2.$value;
console.log(`Received string: ${str}`);
break;
default:
console.log("No data available");
}TypeScript 对比
TypeScript 使用 Promise 和 async/await 处理异步操作。Targo 使用 chan 和 go() 实现并发。
Fixed<T, N> - 固定大小数组
Fixed 是固定大小的数组,对应 Go 的数组类型 [N]T。
创建 Fixed 数组
typescript
// 使用 Fixed.of() 创建
let rgb = Fixed.of(255, 128, 64); // Fixed<int, 3>
// 使用数组字面量(需要类型注解)
let point: Fixed<float64, 2> = [10.5, 20.3];
// 创建零值数组
let zeros = zero<Fixed<int, 5>>(); // [0, 0, 0, 0, 0]访问和修改
typescript
let arr = Fixed.of(1, 2, 3, 4, 5);
// 索引访问
let first = arr[0]; // 1
arr[2] = 10; // 修改元素
// 长度(编译时常量)
console.log(arr.length); // 5多维数组
typescript
// 3x3 矩阵
let matrix: Fixed<Fixed<int, 3>, 3> = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
// 访问元素
let element = matrix[1][2]; // 6
matrix[0][0] = 10;注意
Fixed 数组的大小在编译时确定,不能动态改变。如果需要动态大小,使用 slice<T>。
Array<T> - Runtime 数组
如果你需要 JavaScript 风格的数组方法(如 push、map、filter),可以使用 Array<T>。
创建 Array
typescript
// 使用类型注解
let nums: Array<int> = [1, 2, 3, 4, 5];
// Array 继承自 slice,可以使用所有 slice 方法
console.log(nums.len()); // 5JavaScript 风格的方法
typescript
let nums: Array<int> = [1, 2, 3];
// push - 追加元素
nums.push(4);
nums.push(5, 6);
// pop - 移除最后一个元素
let last = nums.pop(); // 6
// map - 转换元素
let doubled = nums.map(x => x * 2); // [2, 4, 6, 8, 10]
// filter - 过滤元素
let evens = nums.filter(x => x % 2 == 0); // [2, 4]
// reduce - 归约
let sum = nums.reduce((acc, x) => acc + x, 0); // 15
// find - 查找元素
let found = nums.find(x => x > 3); // 4
// includes - 检查是否包含
let hasThree = nums.includes(3); // true性能考虑
Array<T> 的方法有运行时开销。如果追求极致性能,使用 slice<T> 和手动循环。
slice 和 Array 的转换
typescript
// Array 是 slice 的子类型,可以自动向上转型
let arr: Array<int> = [1, 2, 3];
let s: slice<int> = arr; // ✅ 自动转换
// slice 转 Array 需要类型断言
let s2: slice<int> = [1, 2, 3];
let arr2 = s2 as Array<int>; // 类型断言性能考虑
slice 容量管理
typescript
// 预分配容量可以减少内存分配次数
let items = slice.make<int>(0, 1000); // 长度 0,容量 1000
for (let i = 0; i < 1000; i++) {
items = slice.append(items, i); // 不会触发重新分配
}map 预分配
typescript
// 如果知道大致大小,可以预分配
let cache = map.make<string, int>(100); // 提示容量为 100避免不必要的复制
typescript
// ❌ 不好:每次都复制整个 slice
function processItems(items: slice<int>): void {
let copy = slice.make<int>(items.len());
slice.copy(copy, items);
// ...
}
// ✅ 好:直接使用 slice(共享底层数组)
function processItems(items: slice<int>): void {
// 直接使用 items
for (const item of items) {
// ...
}
}常见陷阱
slice 的共享底层数组
typescript
let original = slice.of(1, 2, 3, 4, 5);
let sub = original.slice(1, 4); // [2, 3, 4]
// 修改 sub 会影响 original
sub[0] = 100;
console.log(original); // [1, 100, 3, 4, 5]map 的并发访问
typescript
// ❌ 不安全:多个 goroutine 同时访问 map
let cache = map.make<string, int>();
go(() => {
cache.set("key", 42); // 可能导致 panic
});
go(() => {
let val = cache.get("key"); // 可能导致 panic
});
// ✅ 安全:使用 channel 或 sync.Mutex 保护channel 死锁
typescript
// ❌ 死锁:无缓冲 channel 在同一个 goroutine 中发送和接收
let ch = chan.make<int>();
ch.send(42); // 阻塞,等待接收
let val = ch.receive(); // 永远不会执行
// ✅ 正确:使用有缓冲 channel 或在不同 goroutine 中操作
let ch2 = chan.make<int>(1);
ch2.send(42); // 不阻塞
let val2 = ch2.receive(); // 成功接收类型映射表
| Targo 类型 | Go 类型 | 说明 |
|---|---|---|
slice<T> | []T | 动态数组 |
Array<T> | []T + runtime | 带 JS 方法的 slice |
map<K, V> | map[K]V | 键值映射 |
chan<T> | chan T | 通道 |
Fixed<T, N> | [N]T | 固定大小数组 |
下一步
参考资源
- 集合类型迁移指南 - 从旧 API 迁移
- Go Slices 详解 - Go 官方博客
- Go Maps 详解 - Go 官方博客