“主类型支持模块化错误定位和泛型库复用;现代语言常让 HM 推断服务于代数数据类型构造与模式,但和、积、递归的数据定义不是 HM 核心推断规则的前置。双向类型检查则以局部的 synthesis…”
形式陈述 ​
一个代数数据类型可由有限个构造子描述为
外层和类型表示值从
例如
分别对应 None | Some A 与 Nil | Cons(A, List A)。构造器是和类型的注入再配合字段元组;模式匹配是相应的消去形式:它先依据构造子选择分支,再把该分支携带的字段绑定给分支体。模式匹配是否穷尽、是否有不可达分支,是建立在类型定义之上的静态检查问题,不是构造子声明本身。
“参数化”“递归”“代数”描述不同维度。Option A 对类型参数
直觉 ​
读取一个代数数据类型可以先问两个问题:当前值是哪一种情况,每种情况又带着哪些材料。构造子的集合回答前一个问题,字段列表回答后一个问题。类型声明因此像一张封闭的结构图,而不是一串互不相关的类名;程序消费值时必须沿这张图逐个处理可能分支。
所谓“代数”来自类型构造的形状运算。和对应选择,积对应组合,递归让同一形状继续出现在下一层。这个视角解释了为什么编译器常把值实现为“标签加负载”,也解释了模式匹配为何能同时恢复分支身份与字段类型。
例子与边界 ​
二叉树可定义为 Empty | Node A (Tree A) (Tree A),对应方程
Empty 选择单位分支,Node 选择带三个字段的分支。求节点数时,Empty 返回零,Node 分支递归处理两棵子树;不同分支的字段数和字段类型由构造器决定,不能在 Empty 分支读取不存在的元素。
并非任何可在语言中声明的类型都因此成为代数数据类型。函数类型表达从输入到输出的行为,存在类型隐藏某个表示,带私有状态的对象通过接口封装操作;它们不能仅因语法上有一个类型名就还原成有限构造子的和。广义代数数据类型还允许构造器返回带特定索引的结果类型,例如某构造器只生成 Expr Int;这需要等式约束与索引精化,超出了本页的普通参数化定义。
递归方程也不自动带来良基性。一个允许无限展开或负位置递归的类型可以是递归类型,却不一定是由有限构造步骤生成的归纳类型。因此不能从“写成和与积的递归方程”直接推出结构归纳或终止的 fold。
推论与应用 ​
代数数据类型把数据定义与按形状分支的程序组织在一起。编译器可据构造子集合检查模式覆盖,按标签布局运行时表示,并利用单构造子或无负载分支做表示优化;这些算法化工作必须保持声明式构造与消去规则的含义。
在函数式程序设计中,语法树、配置状态、协议消息和错误结果都常用代数数据类型建模。它们也为结构递归提供清晰的输入形状,但只有进一步满足严格正性和良基生成条件时,才获得归纳类型所保证的递归与证明原则。
参考资料
- Benjamin C. Pierce, Types and Programming Languages, MIT Press, 2002,variants, products, and recursive types。
- Robert Harper, Practical Foundations for Programming Languages, 2nd ed., Cambridge University Press, 2016,sum, product, recursive, and inductive types。
- Luca Cardelli and Peter Wegner, “On Understanding Types, Data Abstraction, and Polymorphism,” ACM Computing Surveys 17(4), 1985,type constructors and data abstraction terminology。