仓颉语言 Iterable 与 Iterator 完全指南:从 for-in 语法糖到自定义可迭代类型
仓颉语言 Iterable 与 Iterator 完全指南:从 for-in 语法糖到自定义可迭代类型
导读
在仓颉(Cangjie)编程语言中,for-in 并非一套孤立的语法魔法,而是建立在 Iterable 与 Iterator 两个核心接口之上的统一迭代机制。本文以标准库 std.collection 与 core 包为事实依据,完整剖析 for-in 的底层展开过程、Iterable/Iterator 的接口契约,并结合仓库源码与可运行样例,讲解 Array、ArrayList、HashSet、HashMap 等容器如何接入该机制,以及如何为自己定义的类型实现可迭代能力。读完本文,你将能够把任何容器或自定义类型无缝接入仓颉的统一遍历体系,并熟练运用迭代器扩展方法与集合操作函数。
Iterable 与 Iterator:for-in 背后的两个核心接口
在仓颉中,Range、Array、ArrayList 都可以直接使用 for-in 遍历。它们的共同点在于都实现了名为 Iterable 的内置接口——for-in 语法正是由该接口支撑的。该接口的核心定义如下(core 包接口文档 中给出的完整声明与下文一致):
interface Iterable<T> {
func iterator(): Iterator<T>
...
}
iterator 函数要求返回一个 Iterator<T> 类型的实例。Iterator 是另一个内置 interface,其核心形式如下:
interface Iterator<T> <: Iterable<T> {
mut func next(): Option<T>
...
}
从接口定义可以看出两层关键信息:
Iterable<T>只声明了一个方法iterator(),它不直接负责产生元素,而是把"如何逐个产出元素"的职责交给返回的Iterator<T>。Iterator<T>同时继承自Iterable<T>,并以next()作为核心方法,每次调用返回Option<T>:遍历未结束时返回Some(元素),到达末尾后返回None。
值得注意的是,在仓库的 core 包类文档 中,Iterator 实际被定义为抽象类而非接口:
public abstract class Iterator<T> <: Iterable<T>
它提供 next(): Option<T> 与 iterator()(返回迭代器自身)两个方法。标准库在此基础上派生了具体的迭代器实现,例如 ArrayIterator(数组迭代器,public class ArrayIterator<T> <: Iterator<T>)与 HashMapIterator(public class HashMapIterator<K, V> <: Iterator<(K, V)>)。这意味着"迭代器"在仓颉标准库中是一套有具体实现支撑的成熟体系,而非仅有接口空壳。
迭代的终点语义:Option 与 None
next() 返回 Option<T> 是理解整个迭代机制的关键。Option 是仓颉内置的枚举类型(见 core 包枚举文档),Some(v) 表示取到了元素 v,None 表示迭代已到尽头。正因为有 None 作为明确的终止信号,for-in 和手写 while 循环才可能做到语义完全等价。
for-in 的等价展开:语法糖背后的 while 循环
任何实现了 Iterable 接口类型的实例,都可以使用 for-in 语法进行遍历。例如下面这段遍历数组的代码:
let list = [1, 2, 3]
for (i in list) {
println(i)
}
它等价于如下形式的 while 代码:
let list = [1, 2, 3]
var it = list.iterator()
while (true) {
match (it.next()) {
case Some(i) => println(i)
case None => break
}
}
两段代码的对应关系一目了然:
for (i in list)中的list会先通过list.iterator()取得迭代器;- 循环体每轮调用
it.next()取下一个元素; match对Option进行分支处理——Some(i)时把元素绑定给循环变量i并执行循环体,None时break结束循环。
因此,"for-in 能遍历哪些类型"这一问题,最终等价于"哪些类型实现了 Iterable"。只要实现 iterator() 与 next() 这两个方法,for-in 语法无需任何额外适配即可生效。
另一种等价写法:while 表达式中的模式匹配
除了上述 while (true) + match 的写法,仓颉还提供了一种更简洁的等价形式——在 while 表达式的条件中直接使用模式匹配:
let list = [1, 2, 3]
var it = list.iterator()
while (let Some(i) <- it.next()) {
println(i)
}
while (let Some(i) <- it.next()) 的含义是:反复将 it.next() 的结果与 Some(i) 模式进行匹配,一旦匹配成功,就把解包出的元素绑定给 i 并执行循环体;当 next() 返回 None(匹配失败)时,循环自动终止。这与 match/break 写法在语义上完全等价,是实际开发中推荐的高可读性写法。
标准库中实现了 Iterable 的容器类型
文档明确说明:Array、ArrayList、HashSet、HashMap 类型都实现了 Iterable,因此它们都可以直接用于 for-in 或 while 遍历。这一结论在标准库源码中得到充分印证:
- ArrayList 的
iterator()方法:public func iterator(): Iterator<T>,返回该 ArrayList 中元素的迭代器; - HashMap 的
iterator()方法:返回HashMapIterator<K, V>; - HashSet 的
iterator()方法:返回Iterator<T>。
更一般地,Iterable 是仓颉集合抽象体系的根。仓库 core 包接口文档 中的 Collection 接口直接继承自 Iterable:
public interface Collection<T> <: Iterable<T> {
prop size: Int64
func isEmpty(): Bool
func toArray(): Array<T>
}
也就是说,凡是实现 Collection 的容器(如 Deque、List、Set、Map 等家族),天然继承了可迭代能力。各容器在"是否可变、是否有序"等特性上存在差异,参见 集合类型概述:
| 类型名称 | 元素可变 | 增删元素 | 元素唯一性 | 有序序列 |
|---|---|---|---|---|
Array<T> |
Y | N | N | Y |
ArrayList<T> |
Y | Y | N | Y |
HashSet<T> |
N | Y | Y | N |
HashMap<K, V> |
K: N, V: Y | Y | K: Y, V: N | N |
实战:遍历 HashSet 并校验元素
由于 HashSet 内部元素无序,遍历它尤其能体现 Iterator 的通用性。仓库实测样例 sample_hashset_add_iterator_remove.md 展示了"迭代 + 成员校验"的完整流程:
import std.collection.*
main() {
var set: HashSet<String> = HashSet<String>()
set.add("apple")
set.add("banana")
set.add("orange")
set.add("peach")
var itset = set.iterator()
while(true) {
var value = itset.next()
match(value) {
case Some(v) =>
if (!set.contains(v)) {
print("Operation failed")
return 1
} else { println(v) }
case None => break
}
}
set.remove("apple")
println(set)
return 0
}
由于 Set 中元素的顺序不固定,运行结果可能如下(仅示意):
apple
banana
orange
peach
[banana, orange, peach]
注意这里同样是"iterator() 取迭代器 → next() 逐个取元素 → None 终止"的经典模式,与 for-in 的展开形式一一对应。
为自定义类型实现 Iterable:让 for-in 支持你的类型
文档开篇即指出:对于开发者自定义的类型,也能实现类似的遍历操作。基于 Iterable 与 Iterator 的接口契约,让自定义类型接入 for-in 只需两步:
- 定义一个继承
Iterator<T>的迭代器类,实现next(): Option<T>,在内部维护遍历状态; - 让自定义类型实现(或扩展出)
Iterable<T>接口,在iterator()中返回上述迭代器实例。
以下是一个围绕接口契约编写的示意实现(具体语法细节以仓颉语言规范为准):
// 1. 自定义迭代器:继承 Iterator<Int64>,维护游标
class MyRangeIterator <: Iterator<Int64> {
let end: Int64
var current: Int64
init(start: Int64, end: Int64) {
this.current = start
this.end = end
}
public mut func next(): Option<Int64> {
if (current <= end) {
let value = current
current += 1
return Some(value)
}
return None
}
}
// 2. 自定义容器类型:实现 Iterable<Int64>
class MyRange <: Iterable<Int64> {
let start: Int64
let end: Int64
init(start: Int64, end: Int64) {
this.start = start
this.end = end
}
public func iterator(): Iterator<Int64> {
return MyRangeIterator(start, end)
}
}
main() {
for (i in MyRange(1, 5)) {
println(i)
}
}
一旦 MyRange 实现了 Iterable,它便与 Array、ArrayList 等标准容器一样,可以直接被 for-in、while (let Some(i) <- it.next()) 以及下文介绍的各类迭代器操作函数使用,无需任何额外的桥接代码。这正是仓颉"统一迭代协议"的设计精髓:能力来自接口实现,而非针对每种类型的特殊处理。
Iterator 的丰富扩展方法:中间操作与终止操作
在仓库 core 包类文档 中,Iterator<T> 通过 extend<T> Iterator<T> 提供了大量扩展方法。这些方法分为两类:
- 中间操作(intermediate operations):产生一个新的迭代器,如 skip()、map()、filter()、take()、step()、enumerate()、flatMap() 等;
- 终止操作(terminal operations):基于迭代结果直接计算出值,不再产生新迭代器,如 all()、any()、count() 等。
每种迭代器方法都会按需消耗迭代器中不同数量的元素。仓库实测样例 sample_iterator.md 展示了它们与 pipeline 表达式(|>)组合使用的典型场景:
import std.collection.*
main() {
let arr = [-1, 2, 3, 4, 5, 6, 7, 8, 9]
arr |> filter{a: Int64 => a > 0} |> // filter -1
step<Int64>(2) |> // [2, 4, 6, 8]
skip<Int64>(2) |> // [6, 8]
forEach<Int64>(println)
let str = arr |> filter{a: Int64 => a % 2 == 1} |> collectString<Int64>(delimiter: ">")
println(str)
println(arr |> contains(6_i64))
return 0
}
运行结果为:
6
8
3>5>7>9
true
这段代码清晰地展示了迭代器的链式处理能力:filter 筛除负数 → step(2) 每隔一个元素取样 → skip(2) 跳过前两个 → forEach 逐元素打印;随后 collectString 将奇数元素以 ">" 为分隔符拼接成字符串,contains 判断是否包含指定元素。可以看到,Iterable/Iterator 不仅是遍历语法的基础,更是仓颉函数式集合处理流水线的起点。
集合包中面向 Iterable 的实用函数
std.collection 包在 collection_package_overview.md 中登记了大量以 Iterable 为输入的操作函数(完整签名见 collection_package_function.md)。这些函数大多采用柯里化形式,天然适配 pipeline 表达式,主要包括:
| 函数 | 功能 |
|---|---|
| collectArrayList / collectArray / collectHashSet / collectHashMap | 将迭代器转换为对应的容器类型 |
| collectString | 将元素拼接为字符串(支持 delimiter 分隔符) |
| all / any / none | 全量/存在/全不满足条件判断 |
| first / last / at / count / isEmpty | 取头尾元素、取指定位置元素、计数、判空 |
| max / min | 求最大/最小元素(要求元素实现 Comparable) |
| filter / map / filterMap / flatMap / flatten / concat / zip | 筛选、映射、扁平化、串联、合并迭代器 |
| fold / reduce / forEach / inspect | 折叠归约、逐元素执行操作、旁路观测 |
| skip / step / take | 跳过/步进/截取指定数量元素(参数非法时抛 IllegalArgumentException) |
| contains | 判断是否包含指定元素(要求元素实现 Equatable) |
以 skip 的参数语义为例:count < 0 时抛出 IllegalArgumentException;count == 0 时等价于原迭代器;count 小于迭代器大小时跳过前 count 个元素;count 大于等于迭代器大小时得到空迭代器。这些边界行为在源码文档中均有明确约定,使用时值得留意。
小结
仓颉语言以 Iterable/Iterator 这对接口构建了统一、可扩展的迭代协议:
- 语法层:
for-in是iterator()+next()+Option匹配的语法糖,可等价展开为while循环; - 契约层:
Iterable只要求实现iterator(),Iterator要求实现next(): Option<T>,None是统一的终止信号; - 生态层:Array、ArrayList、HashSet、HashMap 等所有标准容器均实现了
Iterable,配合迭代器扩展方法、pipeline表达式与集合包操作函数,可组合出强大的数据处理流水线; - 扩展层:自定义类型只要遵循接口契约,即可无缝接入
for-in与全部迭代器工具函数。
无论你是编写业务代码还是设计自己的容器类型,掌握 Iterable 与 Iterator 都是深入理解仓颉集合体系与遍历语法的关键一步。更完整的接口族(Collection、List、Set、Map 等)定义可进一步参阅 core 包接口文档 与 std.collection 包文档。