- 编程语言
- 编译器
- 云原生
- 语言运行时
【免费下载链接】wing
A programming language for the cloud ☁️ A unified programming model, combining infrastructure and runtime code into one language ⚡
本文以 Wing 语言标准库Set/MutSet集合类型为核心,完整解读其 API 设计、不可变与可变两种语义的差异、集合相等性规则,并结合仓库内 10 个测试用例及其 hangar 测试快照,展示如何通过模拟器(sim)平台验证这些行为。读完本文,你将能熟练使用 Wing 中的集合类型进行去重存储、成员查询、复制转换与批量测试,并理解快照式测试的输出结构。
一、从一个测试快照说起
在仓库的测试工具链 hangar 中,保存着标准库测试的预期输出快照。其中tools/hangar/__snapshots__/test_corpus/sdk_tests/std/set.test.w_test_sim.md记录了set.test.w在 sim(模拟器)平台下运行wing test的完整 stdout:
pass ─ set.test.wsim » root/Default/test:add() pass ─ set.test.wsim » root/Default/test:clear() pass ─ set.test.wsim » root/Default/test:copy() pass ─ set.test.wsim » root/Default/test:copyMut() pass ─ set.test.wsim » root/Default/test:delete() pass ─ set.test.wsim » root/Default/test:equality pass ─ set.test.wsim » root/Default/test:has() pass ─ set.test.wsim » root/Default/test:mutability pass ─ set.test.wsim » root/Default/test:size() pass ─ set.test.wsim » root/Default/test:toArray() Tests 10 passed (10) Snapshots 1 skipped Test Files 1 passed (1) Duration <DURATION>这份快照表面上是几行测试日志,实际上浓缩了 Wing 集合类型的全部核心 API:add、clear、copy、copyMut、delete、equality、has、mutability、size、toArray。以下逐一展开它们的语言语义、实现原理与测试证据。
二、Set 与 MutSet:两种可变性语义
2.1 语言层面的类型定义
在 Wing 语言参考docs/api/05-language-reference.md中,集合是六大容器类型之一:
| 类型名 | 说明 |
|---|---|
Set<T> | set 类型(无序的唯一元素集合,默认不可变) |
MutSet<T> | 可变 set 类型 |
声明方式为字面量构造:
let z = Set<num>[1, 2, 3]; // 不可变 set let zm = MutSet<num>[1, 2, 3]; // 可变 set核心语义:set 跟踪的是唯一值的集合——同一个值只能被加入一次。按官方 by-example 文档docs/by-example/11-sets.md的说明,Set 默认不可变(Set<T>),需要变更操作时使用MutSet<T>;重复字面量会被自动去重:
let unqiueNumbers = MutSet<num>[1, 2, 3, 3, 3]; // 实际仅包含 1、2、3 unqiueNumbers.add(4); unqiueNumbers.delete(1); let uniqueStrings = Set<str>["unique", "values", "values"]; // 去重后为 ["unique", "values"]2.2 SDK 中的类型实现
Set与MutSet在标准库中的实现位于packages/@winglang/sdk/src/std/set.ts。文件头注释说明:这些类用于为 Wing 提供 JS 原生Set类的 JSII 子集,不应当被用户直接消费——用户在 Wing 源码中通过Set<T>/MutSet<T>语法使用,编译器负责将其映射到 SDK 类型。
两个类都声明了私有构造函数(private constructor() {}),意味着用户代码无法直接new,只能通过语言语法构造。每个类都带有_toInflightType()静态方法,通过InflightClient.forType()生成 inflight 运行时类型,保证集合类型在 preflight 与 inflight 两个阶段都能被序列化与使用。
2.3 API 总览
依据 SDK 源码与 API 参考docs/api/04-standard-library/std/set.md,两个类型对外暴露的成员如下:
Set<T>(不可变)
| 成员 | 签名 | 说明 |
|---|---|---|
size | num(属性) | 集合长度 |
has(value) | bool | 判断元素是否存在 |
toArray() | Array | 生成与集合内容一致的不可变数组 |
copyMut() | MutSet | 生成内容相同的可变集合 |
MutSet<T>(可变)
| 成员 | 签名 | 说明 |
|---|---|---|
size | num(属性) | 集合长度 |
has(value) | bool | 判断元素是否存在 |
add(value) | MutSet | 向集合添加值 |
delete(value) | bool | 从集合移除值;若值原本存在返回true,否则返回false |
clear() | void | 清空集合所有元素 |
copy() | Set | 生成内容相同的不可变集合 |
toArray() | Array | 生成与集合内容一致的不可变数组 |
注意add的返回值类型被声明为MutSet(便于链式调用),而delete返回布尔值标记是否真的删除了元素。
2.4 宏替换的实现细节
在packages/@winglang/sdk/src/std/set.ts中,copyMut、toArray、copy三个方法体都只是throw new Error("Macro"),真正的实现由@macro注解在编译期展开:
copyMut()标注为@macro new Set($self$)——编译为用原集合内容构造一个新的Set;toArray()标注为@macro [...($self$)]——编译为数组展开语法,生成与原集合等价的数组;MutSet.copy()同样标注为@macro new Set($self$)——把可变集合拷贝为不可变Set。
其余方法(size、has、add、delete、clear)则是抽象占位(throw new Error("Abstract")),由编译器生成的运行时 JS 代码提供真实行为,与 JS 原生Set的语义保持一致。这是 Wing“统一 preflight/inflight 模型”在容器类型上的体现:同一套 API 在两个阶段行为一致。
三、10 个测试用例逐项剖析
测试源码位于tests/sdk_tests/std/set.test.w,它同时包含顶层直接执行的断言(文件加载时即运行)与 10 个test块(由测试框架调度执行)。快照中列出的正是这 10 个 test 块。
3.1toArray():集合转数组
let mySet = Set<num> [1, 2, 3]; let myArrayFromSet = mySet.toArray(); assert(myArrayFromSet.at(0) == 1); assert(myArrayFromSet.length == mySet.size); assert(myArrayFromSet.length == 3);覆盖场景包括:
- 普通不可变
Set<num>转数组,逐下标断言元素; MutSet<str>转数组;- 空集合
Set<bool>[]转空数组Array<bool>[]; - 元素本身是数组的集合
MutSet<Array<str>>(集合元素可以是任意类型,包括容器类型)。
这验证了toArray()的“浅拷贝不可变数组”语义:数组内容与原集合一致,且数组长度恒等于集合的size。
3.2equality:可变性不影响相等性
test "equality"{ assert(Set<num>[1, 2] == MutSet<num>[1, 2]); assert(Set<bool>[true, false] == MutSet<bool>[false, true]); }这里直接对应语言参考docs/api/05-language-reference.md第 6.1.2 节“集合类型”的相等性规则:
- 两个集合相等当且仅当元素个数相同且每个元素按各自类型的相等规则一一对应;元素顺序只在
Array/MutArray中有意义,集合是无序的; - 集合的可变性不影响相等性——
MutArray可等于内容相同的Array,同理MutSet可等于内容相同的Set; - 只有同一种容器“种类”才能比较相等(Array 不能等于 Map,Set 不能与 Array 比较)。
测试中Set<bool>[true, false] == MutSet<bool>[false, true]还额外验证了集合的无序性:元素顺序不同但内容相同,依然相等。
3.3mutability:可变集合的完整操作链
test "mutability" { let openings = MutSet<str> ["A Cruel Angel's Thesis", "Lilium", "Unravel", "TOP"]; let immutOpenings: Set<str> = openings.copy(); // 可变 → 不可变 assert(immutOpenings.copyMut() == openings); // 不可变 → 可变,内容仍相等 openings.add("Abnormalize"); assert(openings.has("Abnormalize")); // 新增可查 openings.delete("TOP"); assert(openings.has("TOP") == false); // 删除后不可查 openings.clear(); assert(openings.size == 0); // 清空后大小为 0 }这个用例把copy、copyMut、add、has、delete、clear、size串成一条完整的状态转换链路,验证了可变与不可变集合之间可以互相拷贝转换,且转换前后内容相等。
3.4size():长度统计
test "size()" { let maleVA = Set<str> ["Kenjiro Tsuda", "Akira Ishida", "Yoshitsugu Matsuoka"]; assert(maleVA.size == 3); let femaleVA = MutSet<str>["Saori Hayami", "Miyuki Sawashiro"]; assert(femaleVA.size == 2); femaleVA.add("Maaya Sakamoto"); assert(femaleVA.size == 3); // 添加后 size 增加 femaleVA.clear(); assert(femaleVA.size == 0); // 清空后 size 归零 }同时验证不可变集合与可变集合的size属性,以及add/clear对 size 的联动影响。
3.5has():成员存在性查询
test "has()" { let genre = Set<str> ["isekai", "mecha", "cyberpunk"]; assert(genre.has("drama") == false); assert(genre.has("mecha")); let mutGenre = MutSet<str> ["rom-com", "sports", "sci-fi"]; mutGenre.delete("rom-com"); mutGenre.add("psychological"); assert(mutGenre.has("psychological")); assert(mutGenre.has("rom-com") == false); }验证has对存在元素返回true、对不存在元素返回false,且删除后立即不可查、新增后立即可查。
3.6copyMut():不可变集合 → 可变集合
test "copyMut()" { let talkingQuirks = Set<str> ["dattebane", "battebayo", "dattebasa"]; assert(talkingQuirks.copyMut() == MutSet<str> ["dattebane", "battebayo", "dattebasa"]); }验证Set上唯一的“转可变”入口:copyMut()产出的MutSet与原Set内容相等(再次印证可变性不影响相等性)。
3.7add():添加元素
test "add()" { let evaRebuild = MutSet<num> [1.11, 2.22, 3.33]; evaRebuild.add(3.0+1.0); // 数值表达式作为元素 assert(evaRebuild.has(3.0+1.0)); assert(evaRebuild == MutSet<num>[1.11, 2.22, 3.33, 3.0+1.0]); }值得注意的两点:一是集合元素可以是任意表达式(这里是用3.0+1.0计算出的num),二是add之后与字面量集合直接比较相等。
3.8delete():删除元素及其返回值
test "delete()" { let studios = MutSet<str> ["Gainax", "Ghibli", "Production I.G.", "Shaft"]; assert(studios.delete("Gainax")); // 删除已存在元素 → true assert(studios.has("Gainax") == false); assert(studios.delete("Sunrise") == false); // 删除不存在元素 → false assert(studios.size == 3); }精确验证delete的布尔返回值语义:目标值原本在集合中返回true,否则返回false(与 JSSet.prototype.delete一致)。
3.9clear():清空集合
test "clear()" { let demographics = MutSet<str> ["shounen", "shoujo", "josei", "seinen"]; demographics.clear(); assert(demographics.size == 0); demographics.add("kodomo"); demographics.clear(); // 再次清空 assert(demographics.has("kodomo") == false); }验证clear()幂等且彻底:清空后size == 0,重新添加后再清空,元素同样不可查。
3.10copy():可变集合 → 不可变集合
test "copy()" { let acronyms = MutSet<Map<str>> [{"SEL" => "Serial Experiments Lain", "NGE" => "Neon Genesis Evangelion"}]; let copyAcronyms = acronyms.copy(); assert(copyAcronyms == Set<Map<str>>[{"SEL" => "Serial Experiments Lain", "NGE" => "Neon Genesis Evangelion"}]); acronyms.add({"DomeKano" => "Domestic na Kanojo"}); let copyAcronymsNew = acronyms.copy().copyMut(); // 链式拷贝 assert(copyAcronymsNew == acronyms); }验证两点:copy()产出不可变Set且内容与原MutSet相等;copy().copyMut()链式转换后依然与原集合相等。同时该用例使用了MutSet<Map<str>>,证明集合元素可以是复杂容器(如Map),并且深度嵌套内容也能参与相等比较。
四、快照是如何生成的:hangar 测试框架
要理解快照文件本身,需要了解 Wing 的 E2E 测试工具 hangar(位于tools/hangar)。它对tests/sdk_tests、tests/valid、tests/doc_examples等目录下的每个.w文件自动生成测试用例并执行:
4.1 测试用例的自动生成
tools/hangar/src/generate_tests.ts递归扫描源目录中的.w文件,为每个文件生成两类 vitest 用例:
wing compile -t tf-aws:编译为 Terraform AWS 目标,产出set.test.w_compile_tf-aws.md这类编译快照;wing test -t sim:在模拟器平台运行测试,产出_test_sim.md这类运行快照。
4.2 测试执行的实现
tools/hangar/src/generated_test_targets.ts中的testTest()负责运行wing test -t sim(只有入口文件.test.w、main.w等才会真正执行测试),把 stdout/stderr 写入fileMap,再调用createMarkdownSnapshot落盘为 Markdown 快照。这就是set.test.w_test_sim.md里stdout.log区块的来源。
4.3 快照内容为何如此“干净”
注意快照中的Duration <DURATION>——这不是神秘占位符,而是tools/hangar/src/utils.ts中sanitizeOutput()的规范化处理:为了让快照在不同机器上稳定复现,它会把测试耗时(Duration \d+m[\d.]+s)、时间戳、绝对路径、端口号、IP 等环境相关输出统一替换为<DURATION>、<TIMESTAMP>、<ABSOLUTE>、<PORT>等占位符。这也是快照工程中消除非确定性输出的标准做法。
同时,set.test.w编译到 tf-aws 平台时没有任何云资源(纯计算逻辑),所以对应的编译快照只有最小化的main.tf.json(仅含awsprovider 声明)——这从侧面说明 Set 是纯语言层面的容器类型,编译时不会产生基础设施资源。
五、如何运行这些测试
仓库中该测试可通过 hangar 生成的目标执行(由package.json/turbo.json编排),核心命令为:
# 在 hangar 包内运行 sdk_tests 相关的 sim 测试(实际命令以仓库脚本为准) wing test -t sim tests/sdk_tests/std/set.test.w手动验证集合行为最简单的方式,是像 by-example 文档那样编写一个main.w,然后用 Wing 控制台或模拟器运行:
wing it # 本地运行 Wing 控制台 # 或 wing test -t sim main.w在docs/by-example/11-sets.md的示例中,MutSet的toArray()与size会分别输出[2,3,4]和3,印证了 add/delete 后集合状态的变化。
六、实用要点总结
- 默认不可变,按需可变:
Set<T>用于只读场景,需要增删时显式使用MutSet<T>;这符合 Wing 容器类型“immutable by default”的设计原则。 - 四个转换/读取成员:
toArray()(转数组)、copy()(变不可变)、copyMut()(变可变)、has()(查询)在两类集合上按需提供,均通过@macro在编译期展开为 JS 原生操作。 - 相等性不看可变性与顺序:
Set<num>[1, 2] == MutSet<num>[1, 2]成立,Set<num>[1, 2, 3] == Set<num>[3, 2, 1]也成立,这是语言参考第 6.1.2 节明确规定的集合语义。 delete有返回值:返回true表示确实删除了元素,false表示值本就不存在,可用于防御式编程。- 测试快照即文档:
set.test.w_test_sim.md与set.test.w组合起来就是 Set 全部 API 的可执行规格说明——10 个用例覆盖了所有成员与关键边界(空集合、嵌套容器元素、链式拷贝、幂等清空等)。
如果需要进一步研究 API 的完整签名与参数类型,可查阅docs/api/04-standard-library/std/set.md;想深入了解相等性规则在集合上的应用,可阅读docs/api/05-language-reference.md第 6.1.2 节。
- 编程语言
- 编译器
- 云原生
- 语言运行时
【免费下载链接】wing
A programming language for the cloud ☁️ A unified programming model, combining infrastructure and runtime code into one language ⚡
相关推荐
Wing 语言 math 标准库 factorial 函数深度解析:从 SDK 实现到 Hangar 快照测试验证
Wing 语言 math 标准库 factorial 函数深度解析:从 SDK 实现到 Hangar 快照测试验证 math.factorial 是 Wing
编程语言编译器云原生语言运行时Wing 语言 expect 标准库断言 API 全解析:从 hangar 快照看编译产物与测试验证
Wing 语言 expect 标准库断言 API 全解析:从 hangar 快照看编译产物与测试验证 本文围绕 Wing 开源仓库中的 expect 标准库模块
编程语言编译器云原生语言运行时Wing 云编程语言 fs 标准库 JSON 文件读写与 Hangar 快照测试验证
Wing 云编程语言 fs 标准库 JSON 文件读写与 Hangar 快照测试验证 Wing 是一门面向云的编程语言,它的标准库 fs 模块提供了在 pref
编程语言编译器云原生语言运行时
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考