Skip to content

集合类型

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 slice

TypeScript 对比

在 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 使用 Promiseasync/await 处理异步操作。Targo 使用 chango() 实现并发。

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 风格的数组方法(如 pushmapfilter),可以使用 Array<T>

创建 Array

typescript
// 使用类型注解
let nums: Array<int> = [1, 2, 3, 4, 5];

// Array 继承自 slice,可以使用所有 slice 方法
console.log(nums.len());  // 5

JavaScript 风格的方法

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固定大小数组

下一步

参考资源