想象你是快递分拣中心新上任的主管。传送带上,包裹从来不是一种:信封薄而软,纸箱方方正正,圆筒一碰就滚。更麻烦的是,有的包裹里还套着更小的包裹。你的信息系统却只会说一句话:“所有包裹都是纸箱。”于是分拣错误、爆仓、投诉接连不断。这几乎是所有编译器团队都会撞上的墙:类型系统太粗糙,表达不了“一个东西要么是 A、要么是 B、要么是 C”。TVM 在 RELAY IR 中引入代数数据类型(ADT),正是为了打破这堵墙——它让类型系统能描述“多种形状”的数据,并提供一套叫模式匹配的“分拣规则表”,安全高效地把每种形状的处理流程独立出来。本文就用这套分拣中心的视角,把 ADT 的定义、语法、匹配、编译、类型检查到实际应用完整讲一遍。
读懂 ADT,你就拿到了理解 RELAY 类型系统的钥匙:动态形状、循环展开、图匹配优化这些 TVM 核心能力,都建立在“能安全描述和分支处理异构结构”之上。对 AI 从业者而言,这是从“用编译器”迈向“改编译器”的必经一站。
可视化演示
展示RELAY IR中代数数据类型的和类型积类型构造、模式匹配分派及递归类型展开,交互切换查看ADT核心机制。
本节配有交互动效(进入视口后自动加载,约 3KB)。
类型构造:和类型与积类型 和类型取值空间为各构造器基数之和 积类型取值空间为各字段基数之积 |Sum|=|A|+|B| ADT通过构造器组合类型空间
ADT基本概念
你走进快递分拣中心,传送带上一片混乱:信封、纸箱、圆筒三种形状的包裹混在一起。老分拣系统只认识“纸箱”这一种类型,遇到信封就罢工。ADT(代数数据类型)就是为这种情况设计的:它允许类型系统说清楚“我可能是信封、也可能是纸箱、还可能是圆筒”。在 TVM 的 RELAY IR 中,ADT 是表达这类异构结构的基础设施。
ADT 由两种基础拼图构成。积类型(product type)像一张表格:所有字段同时存在,比如纸箱的面单必须有收件人、地址、重量三栏,缺一不可,这是“与”的关系。和类型(sum type)像一张候选清单:一次只能选一个,包裹要么是信封、要么是纸箱、要么是圆筒,这是“或”的关系。每个候选叫一个变体,而“把材料打包成一个变体值”的动作由构造器(constructor)完成。
每个构造器生产的值都会携带一个整数标签,标识自己是第几个变体,类似面单上的类型编号。不同构造器可以携带不同的字段集合:信封带一个地址字段,纸箱带重量字段,圆筒带直径字段。这样,一个 ADT 值 = 标签 + 对应的字段,在运行时既能区分“我是谁”,又能安全读取“我有什么”。
回到分拣中心:积类型是那张必须全部填写的面单,和类型是“今天只来一种包裹”的约束。代数数据类型这个名字听着吓人,实际上就是把“与”和“或”组合起来,让编译器也能像分拣主管一样,清楚地知道传送带上可能出现的每一种情况。
- 和类型 A ⊕ B⊕ 表示不相交并集,取值要么来自 A、要么来自 B,代表“或”的关系。
- 取值个数相加A 有 |A| 种值、B 有 |B| 种值,和类型总共 |A| + |B| 种,对应三种包裹并集的规模。
- 积类型 A × B× 的取值是成对组合 (a, b),代表“与”的关系,对应面单上多个字段同时存在。
- 取值个数相乘每个 a 都能配任意 b,总数是 |A| × |B|,所以叫积类型。
RELAY定义语法
分拣中心要接纳新形状的包裹,第一步不是改造机器,而是更新“包裹类型登记表”:给新类型起名、列出它的每种包装方式、每种包装需要哪些材料。RELAY 中用 data 关键字做同样的登记:
这段声明告诉编译器:存在一个名为 List 的泛型数据类型,它有两种构造方式。Nil 是零参构造器,代表“空列表”,类似一个不需要任何材料就能完成的空包裹;Cons 是两参构造器,接收一个 T 类型元素和另一个 List<T>,类似把一件物品装进一个更小的同型包裹里。冒号左边是构造器名字,右边是函数签名,读作:用这些参数,你能得到这个类型的值。
data 语法与函数定义 def 是并列的顶层语法形式,因此 ADT 可以出现在 RELAY 程序的任意作用域中。RELAY 的解析器对 data 声明提供专门支持,解析后将 ADT 定义注册进全局类型环境,后续表达式才能引用这些构造器。泛型参数 <T> 让一个 ADT 定义能复用于不同类型,例如 List<Tensor> 与 List<Shape>。
登记表填好,分拣系统就知道了三种包裹的存在。data 一词,用贴近数据而非算法的语气告诉编译器:“这几种形状,你以后都会见到,请提前做好准备。”这就为后续的匹配与分拣打好了基础。
| 是“或”,把数据类型的全部变体一一列出,就像登记表上列出每种包装方式。- data 关键字向编译器登记一个新类型的开始,声明后面跟着类型名字。
- 类型参数 (T)List 是泛型类型,T 可被任意具体类型替换,让一个定义服务多种元素。
- 竖线 | 分隔变体读出“或”的味道,
Nil与Cons是两种互斥的构造方式。 - 构造器签名每个构造器声明自己的参数类型,Nil 无参数,Cons 含头部与尾部两参。
data List<T> { Nil : () -> List<T> Cons : (T, List<T>) -> List<T>}
和类型与积类型
面单黏在包裹上,内容物装在里面——两者合在一起,才构成一个可识别的快件。ADT 的内部编码也是同样的思路:每个值 = 标签 + 字段负载,这种形式叫带标签联合(tagged union)。
积类型贡献了“多字段同时存在”的能力。RELAY 的构造器参数在底层被组织成一个元组,例如 Cons(1, tail) 的两个参数就构成一个积:任何时候你都同时拥有头部和尾部。和类型贡献了“多选一”的能力:整个 List 若非 Nil 即 Cons,永远不可能既是 Nil 又是 Cons。
带标签联合的编码方式是:先用一个整数记录当前是哪个变体,再把该变体的字段作为另一个数组存储。消费方拿到值后,先读标签判断变体,再按变体解释字段。RELAY 还提供了 Option 之类的内建和类型,用于表达“可能有值也可能没有”的场景,例如安全地从映射中查键。
当三种包裹在传送带上混行时,分拣机不需要拆开包裹就知道该走哪条道——它只需扫面单的标签。这就是带标签联合的精髓:把“我是谁”的决定权交给显式标签,而不是靠猜。
- tag(v)取出 v 的构造器索引,一个从 0 开始的整数,标识它是第几个变体。
- fields(v)取出 v 携带的字段列表,里面按构造器签名的顺序存放参数。
- 角括号 ⟨·⟩把标签与字段打包成一个整体,共同构成值的运行时表示。
- 编码全貌消费者先看标签、再按需取字段,整个编码过程保证“知道是谁”与“拿到什么”不分离。
模式匹配机制
有了面单标签,分拣机怎么处理每个包裹呢?靠的是一张“分拣规则表”:每条规则写着“如果标签是信封,就走扫描通道;如果标签是纸箱,就走称重通道……”。RELAY 中的 match 表达式就是这张规则表。
match 接受一个要检查的值,以及若干分支。每个分支由两部分组成:左边的模式(pattern)和右边的表达式。模式刻画出一种可能的形状,比如 Nil 或 Cons(h, t)。当被匹配的值与某个模式吻合时,分支表达式就会被执行。
模式中出现的名字不是新建变量,而是从被匹配值的字段中“解构”出来的绑定。比如在 Cons(h, t) 模式中,h 会绑定到列表头部、t 绑定到尾部,这些绑定只在对应分支体内可见。RELAY 的类型检查器还会做穷尽性检查:如果某些构造器没有对应分支,编译会直接报错,防止运行时出现“不知道该怎么办”的包裹。
分拣规则表写全了,任何形状的包裹都有处理通道,没有例外。穷尽性检查让编译器成为你的质检员:漏写一个变体,它当场指出,而不用等包裹卡在传送带上才发现。
- match x对值 x 开启分派,x 必须是某个 ADT 类型的值。
- Nil ⇒ e₀第一个分支的模式是构造器 Nil,命中后执行表达式 e₀。
- 竖线连接分支分支按顺序逐个测试,每个分支对应一个变体。
- Cons(h,t) ⇒ e₁模式里绑定 h、t,对应字段由模式解构赋值,分支体中使用这两个名字。
编译处理流程
登记表、面单、规则表都是给管理层看的;传送带上的机械臂听不懂这些,它们只认挡板位置和推杆动作。ADT 也一样:RELAY 的构造器和 match 是给编译器前端和高层优化 pass 看的,生成最终代码前,它们会被降级成简单的标签整数和分支跳转。
降级的第一步,是把每个构造器调用编码为二元组(标签,字段数组);第二步,是把 match 表达式编译成对标签的连续比较,类似级联的 if-else,或者按标签索引的跳转表;第三步,是把模式中的绑定变量替换为对字段数组的投影访问。经过这三步,复杂的模式匹配就变成了普通的运行时分支。
因为标签是连续整数,RELAY 可以选用 switch 跳转表把匹配优化到 O(1)。字段投影只需根据构造器签名给出的偏移量访问数组元素,几乎零开销。此外,如果一个 ADT 类型只使用过一部分构造器,死代码消除还能删掉永不触发的分支,让生成的代码更紧凑。
面单被翻译成了机械臂能懂的挡板指令,三种包裹最终各就各位。ADT 的抽象在编译过程中被安全地“拆解”了,而这个过程中,模式匹配的安全性一次都没有打折。
- process(x)表示 x 经过模式匹配后的整体行为,是编译产物的抽象描述。
- tag(x) = i读取 x 的构造标签,若等于第 i 个变体的编号则进入该情形。
- proj_i(x)投影操作,按第 i 个构造器的签名从字段数组中取出对应位置的值。
- 替换绑定变量把模式中绑定的变量 \vec{y} 替换成投影结果,原模式表达式变成纯数据访问。
ADT类型检查
任何包裹要登上传送带,先得过质检关:面单上写着信封,内容物就必须是信封该有的样子。ADT 的类型检查规则做的就是这件事——保证构造器用对的参数造出对的值。
类型检查器对每个构造器给出一条规则:如果你有一个类型为 τᵢ 的值 v,那么用第 i 个构造器 Cᵢ 包装 v,得到的就是目标 ADT 类型 D 的值。这条规则保证了从任何构造器出来的值,在类型上都统一为 D,消费者可以放心地匹配。
对 match 表达式的检查,RELAY 要求每个分支产生相同的返回类型,否则合并类型会不一致;同时要求模式覆盖所有构造器,即穷尽性检查。对于泛型 ADT,类型检查器会在实例化时把类型参数替换为具体类型,例如 List<Tensor> 中的 T 被替换成 Tensor,并在替换后验证构造器参数类型仍然匹配。
质检员点头放行后,包裹才允许上流水线。类型规则让“面单与内容一致”不再是口头承诺,而是编译期就被强制执行的铁律——这正是 ADT 比无标签 union 安全的核心原因。
- Γ ⊢ vτᵢ:Γ 是类型环境,⊢ 表示“可推出”,整个前提断言 v 拥有类型 τᵢ。
- 横线(推导规则)表示“前提成立则结论成立”,是自然演绎中的一条推理规则。
- Cᵢ(v)D:第 i 个构造器包装 v 后,产物类型是 ADT 类型 D,与具体参数类型无关。
- 每个构造器一条规则所有变体的构造器都指向同一个目标类型 D,消费者无需关心具体来源。
循环递归表达
最棘手的包裹是套娃:纸箱里还有纸箱,一层套一层,深不见底。以前的分拣机看到这种包裹直接死机,因为它只会处理“一层”的结构。ADT 的递归类型就是为这样的套娃而生的。
在 List 的定义中,Cons 的第二个参数的类型正是 List 本身——定义中出现了自己,这就是递归类型。有了它,任意长度的序列都能表达:最少是 Nil,多一个元素就把 Cons 再套一层。树的表达同理:树要么是一片叶子,要么是一个节点带着几棵子树。
递归类型给了 RELAY 表达循环和深度结构的能力。RNN 的时间步序列可以建模为一个 ADT 列表,网络体作为 Cons 的字段,编译器通过模式匹配对每个时间步应用网络体,生成展开的循环代码。对于固定步数,编译期就能完全展开;对于动态步数,则降级为带运行时的循环执行。
分拣机学会递归后,套娃包裹不再是噩梦:拆开一层,里面露出更小的一层,按同一套规则继续处理,直到遇见最里层的“空包裹”。这种“自己处理自己”的优雅简洁,让 RELAY 第一次真正意义上支持了循环与递归网络结构。
- Nil递归的终止记号。分拣机的“空包裹”,表示序列在这里结束。
- 最内层 Cons(3, Nil)只有一个元素的列表,尾部指向空列表。
- 逐层向外嵌套每次在外层再套一个 Cons,元素 2 与元素 3 所在的小列表相连。
- 完整链表元素 1 套在最外层,得到长度为 3 的列表,整个过程可无限重复。
TVM实际应用
分拣中心终于完成了全面升级:能处理异形包裹、能应对套娃、能根据路况动态调整通道。这套能力放到 TVM 里,就是 RELAY 引入 ADT 后解锁的一连串实际应用。
最直接的应用是动态形状。深度模型推理时 batch size 常常在请求间变化,静态形状编译器只能针对固定形状优化。借助 ADT,形状可以定义为静态维度与动态维度的和类型,编译产物同时覆盖所有情况,让单个内核服务不同 batch 的请求。
ADT 还用于 RELAY 的计算图优化:许多 pass 本身就是“图模式匹配”——在图上查找特定结构并替换,而 ADT 的模式匹配为这种遍历提供了统一且安全的机制。此外,像 Shape::Static(n) 这样的构造器在 pass 中可以作为编译期可证明的信息源,让图优化在合法时开启动态形状相关的简化,在不可能时安全地跳过。
从异形包裹到套娃再到动态路由,ADT 让 RELAY 这台“分拣机”拥有了完整的表达能力:类型安全地描述“可能是什么”,并高效地处理“每一种可能”。这正是现代机器学习编译器处理复杂、动态网络结构时的必备基础。
- Static(n)静态形状,n 是编译期确定的整数,可触发常量折叠与内存规划。
- Dynamic(s)动态形状,s 是符号变量,运行时才绑定具体数值。
- 符号 s用符号表示未知维度,例如请求到达前的 batch size。
- 联合类型维度要么静态、要么动态,编译器按标签分发到两种优化路径。
AI 应用场景
动态形状推理服务编译
阶段:模型编译与部署优化
解决的问题:传统 TVM 对静态形状编译,推理服务请求的 batch size 一旦变化,旧内核失效需要重新编译,造成延迟尖峰。利用 ADT 的 Shape = Static(n) | Dynamic(s),编译产物可以覆盖多档 batch,运行时按标签分发,既保留静态优化又兼顾动态变化。
具体案例:某在线推荐系统在高峰与低谷期 batch 从 1 到 64 波动。使用 RELAY 动态形状路径后,单个内核适配所有 batch,吞吐提升约 30%,启动时编译次数降到接近零。
RNN 循环网络编译
阶段:图优化与代码生成
解决的问题:循环网络的时间步在图中展开会得到超长计算图;不用 ADT 时,手动展开或宏生成都很脆弱。用递归 List 类型表达时间步序列,以模式匹配逐时间步应用网络体,编译器即可生成循环或有限展开。
具体案例:语音识别 LSTM 推理中,时间步长随音频长度动态变化。ADT 列表让编译器生成按时间步循环的内核,内存占用稳定,首次编译时间明显缩短。
图融合优化 Pass
阶段:编译优化(算子融合)
解决的问题:手写遍历计算图判断节点类型容易出错、难以维护。ADT 模式匹配把“识别结构”与“提取字段绑定”统一起来,还附带穷尽性检查,新增算子类型时编译器会提醒补全 pass 分支。
具体案例:TVM Pass 用 match 优雅描述 Conv+BN+ReLU 融合模式,模式中绑定的变量直接用于生成融合算子,融合规则像分拣规则表一样清晰可查。
Python 演示
"""RELAY ADT 的 Python 类比:构造器、标签与模式匹配""" class TagValue: """ADT 底层表示:tag 标签 + fields 字段""" def __init__(self, tag, fields): self.tag = tag self.fields = fields def make_constructor(tag): """构造器工厂:把参数打包成带标签的值""" def constructor(*fields): return TagValue(tag, fields) return constructor # 类型定义:List = Nil | Cons(T, List) (模拟 RELAY data 声明)Nil = make_constructor(0)Cons = make_constructor(1) def match(value, arms): """模式匹配:按 tag 分发到对应分支""" for tag, fn in arms: if value.tag == tag: return fn(*value.fields) raise ValueError("non-exhaustive match: 缺少构造器分支") def length(lst): """递归计算链表长度,对应 RELAY 的 match 写法""" return match(lst, [ (0, lambda: 0), (1, lambda head, tail: 1 + length(tail)), ]) def to_list(lst): """把 ADT 链表转换成 Python 列表""" return match(lst, [ (0, lambda: []), (1, lambda head, tail: [head] + to_list(tail)), ]) # 类型定义:Shape = Static(n) | Dynamic(s) (模拟动态形状)Static = make_constructor(0)Dynamic = make_constructor(1) def shape_desc(shape): """对 Shape 值做模式匹配""" return match(shape, [ (0, lambda n: "static size %d" % n), (1, lambda s: "dynamic symbol %s" % s), ]) def main(): lst = Cons(1, Cons(2, Cons(3, Nil()))) print("length =", length(lst)) print("to_list =", to_list(lst)) print(shape_desc(Static(32))) print(shape_desc(Dynamic("batch"))) if __name__ == "__main__": main()
class TagValue对应 ADT 值的运行时表示:一个标签加一串字段。def make_constructor(tag)构造器工厂,把任意参数打包成带标签的值。Nil = make_constructor(0)零参构造器,标签为 0,无需参数即可构造。Cons = make_constructor(1)两参构造器,标签为 1,参数被冻结在 fields 中。def match(value, arms)对应编译后的标签分发逻辑,逐个比较 tag。if value.tag == tag现场版“扫描面单”,命中后调用对应分支函数。raise ValueError(...)模拟穷尽性检查失败:漏了某个构造器分支。lambda head, tail: 1 + length(tail)模式绑定:从 fields 解构出头和尾并递归。(0, lambda: [])Nil 分支直接返回空列表,是递归的终止条件。shape_desc(shape)对动态形状类型的值做模式匹配,语义与 RELAY 的 Shape ADT 一致。
常见误区
误区“ADT 不就是自定义数据类型吗?跟结构体差不多,无非是把几个字段打包在一起,没什么特别的。”
正解结构体只是积类型,表达“同时拥有字段 A 和 B”;ADT 还包含和类型,表达“要么是 A 要么是 B”。这个“或”的能力,才是表达可选值、动态形状和递归结构的根基。RELAY 里的 List 若不是 Nil 就必然是 Cons,结构体永远表达不了这种二选一。
为何常见:大多数程序员最早接触的自定义类型是 struct/class,容易把“自定义类型”简单等同于“积类型”,忽略了和类型这个关键维度。
误区“match 不就是个好看的 if-else 吗?写得花哨一点而已,功能上跟手写分支没区别。”
正解模式匹配的独特价值在编译期:穷尽性检查保证所有变体被覆盖,模式绑定自动从字段中解构变量,编译器还能将匹配优化为跳转表。这些是手写 if-else 无法自动获得的正确性保证,不是语法层面的甜味剂。
为何常见:从最终生成的指令看,确实都是分支跳转,于是容易忽略编译期间的类型级保证才是模式匹配的灵魂。
误区“ADT 是 Haskell 这类函数式语言才玩的东西,工业编译器用不上,学了也没用。”
正解TVM 的 RELAY IR 正是用 ADT 表达动态形状、循环与递归网络,这是工业级机器学习编译器的核心需求。Rust 的 enum、C++17 的 std::variant 本质上也是 ADT,离工程一点也不远。
为何常见:AI 从业者大多接触 Python/C++,对函数式构造感到疏远,误以为 ADT 与工程实践无关。
误区“C 里的 union 不就是和类型吗?都占一块内存,都可以存不同类型,名字也像。”
正解C union 是无标签联合,运行时不知道当前存的是哪个变体,读错字段就是未定义行为。ADT 的和类型是带标签联合,每个值都携带构造器标签,运行时可以安全区分并读取对应字段。
为何常见:union 与 sum type 的英文名相近(union / disjoint union),内存布局也相似,初学者极易混淆。
误区“每次构造都要打标签,每次匹配都要查标签,肯定比直接操作 struct 慢,编译器不会选它。”
正解标签只是一个小整数,匹配编译成整数比较或跳转表,现代硬件还有分支预测加持,实际开销几乎可忽略。相比它带来的表达力和编译期安全,这点成本是编译器非常乐意支付的。
为何常见:直觉上“额外的信息 = 额外的开销”,但没有考虑到编译期的优化能力和硬件层面的加速机制。
面试题
Q1. 代数数据类型中的“和类型”(sum type)相当于集合运算中的哪种操作?
- A.交集运算
- B.并集运算← 正确答案
- C.补集运算
- D.笛卡尔乘积
答案:B。和类型的取值来自 A 或 B,集合规模相加,与并集运算同构。交集和笛卡尔乘积对应积类型,补集则与类型构造无关。分拣中心的三种包裹就是“并”出来的一个集合。
Q2. 在 RELAY 中定义代数数据类型使用的关键字是?
- A.struct 关键字
- B.class 关键字
- C.data 关键字← 正确答案
- D.enum 关键字
答案:C。RELAY 用 data 声明 ADT,与 def 并列。struct/class 是面向对象语言的概念,enum 虽类似但不等于完整的 ADT。就像分拣中心的登记表,标题写的就是 data。
Q3. 下列哪个类型是典型的和类型(sum type)?
- A.包含两个字段的元组
- B.固定长度的数组
- C.Tensor 张量类型
- D.Option 可空类型← 正确答案
答案:D。Option 要么是 Some 要么是 None,是典型的“多选一”和类型。元组和定长数组是积类型,Tensor 是 RELAY 的内建类型。如同“包裹要么有内容,要么空着”。
Q4. 模式匹配的穷尽性检查主要保证什么?
- A.所有构造器分支都被覆盖← 正确答案
- B.匹配速度达到最优
- C.表达式没有副作用
- D.代码行数最短化
答案:A。穷尽性检查在编译期核对模式分支是否覆盖全部构造器,漏分支直接报错,防止运行时出现未定义行为。它不关注速度、副作用或代码行数。分拣规则表必须写全,漏掉哪种包裹形状都会被当场打回。
Q5. 带标签联合(tagged union)的编码形式是?
- A.仅存储数据字段
- B.标签与数据分离保存
- C.标签与数据绑定成对← 正确答案
- D.标签替换数据类型
答案:C。带标签联合把 tag 和 fields 打包成二元组,消费方先读标签、再根据标签解释字段。标签与数据绑定成对,才能安全区分不同变体。面单和内容物是一体的,扫面单才知道怎么处理包裹。
Q6. 构造器类型规则“如果 v : τᵢ,那么 Cᵢ(v) 的类型是什么”的结论是?
- A.v 保持其原有类型不变
- B.构造器调用得到目标类型 D← 正确答案
- C.构造器调用返回空类型
- D.构造器调用类型不确定
答案:B。规则的前提是 v 的类型为 τᵢ,结论是 Cᵢ(v) 的类型为目标 ADT 类型 D。所有构造器产出的值都统一为 D,消费者才能放心匹配。每种包装方式造出来的都是“同一类包裹”。
Q7. 递归类型 List = Nil | Cons(T, List) 无法直接表达哪种结构?
- A.任意长度的链表
- B.嵌套的树形数据
- C.定长的静态数组← 正确答案
- D.循环网络结构展开
答案:C。递归 List 能表达任意长度链表、嵌套树形和循环网络的时间步展开,但定长静态数组本质是“同时拥有 N 个槽位”的积类型,用张量或元组表达更合适。套娃能套任意多层,却放不进一个固定格子的收纳盒。
Q8. ADT 相比普通结构体的优势有哪些?
- A.表达多种可选变体← 正确答案
- B.模式匹配安全解构← 正确答案
- C.穷尽性编译期检查← 正确答案
- D.自动生成计算图
答案:ABC。ADT 能表达“多选一”的变体、通过模式匹配安全解构字段、并在编译期做穷尽性检查。自动生成计算图是 TVM 框架整体的能力,不是 ADT 本身的职责。结构体只会说“同时有这些字段”,ADT 还能说“要么这种、要么那种”。
Q9. 关于模式匹配的编译降级过程,下列说法正确的有?
- A.按标签整数比较分发← 正确答案
- B.绑定变量投影字段← 正确答案
- C.分支数量必须为偶数
- D.可采用跳转表加速← 正确答案
答案:ABD。匹配被编译成对标签整数的比较分发,模式绑定的变量用字段投影替换,标签连续时可以优化为跳转表。分支数量没有奇偶限制,只要覆盖全部构造器即可。分拣机的机械臂不关心有几条滑道,只关心每条都有对应的挡板动作。
Q10. TVM 的 RELAY 引入 ADT 的目的包括?
- A.表达动态数据形状← 正确答案
- B.表达循环递归结构← 正确答案
- C.替代硬件指令集
- D.提升编译期安全← 正确答案
答案:ABD。RELAY 用 ADT 表达动态形状(如 Shape = Static | Dynamic)、循环递归结构(如 List 表达时间步)并提升编译期安全(穷尽性检查与类型规则)。ADT 是编译器的类型系统能力,与硬件指令集无关。分拣中心升级信息系统,不等于重新发明传送带电机。