仓颉语言 Iterable 与 Iterator 完全指南:从 for-in 语法糖到自定义可迭代类型

原创2026-10-14 19:17:171,820 阅读
文章标签:文档知识库

仓颉语言 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
    }
}

两段代码的对应关系一目了然:

  1. for (i in list) 中的 list 会先通过 list.iterator() 取得迭代器;
  2. 循环体每轮调用 it.next() 取下一个元素;
  3. 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 遍历。这一结论在标准库源码中得到充分印证:

更一般地,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 只需两步:

  1. 定义一个继承 Iterator<T> 的迭代器类,实现 next(): Option<T>,在内部维护遍历状态;
  2. 让自定义类型实现(或扩展出)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> 提供了大量扩展方法。这些方法分为两类:

每种迭代器方法都会按需消耗迭代器中不同数量的元素。仓库实测样例 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 包文档。

登录后查看全文
CangjieCorpus