Skip to content

方法Method

依赖类型 elaboration

施工中 · 内容逐步修订

Dependent type elaboration · Elaboration of dependent types

将表面程序展开为分层Π/Nat/Vec核心,以显式消去子构造向量连接,并由独立核重新检查motive、分支与conversion。

形式陈述 ​

Elaboration 区分便于书写的 surface language 与规则精小的 core theory。可把核心接口写成两种带输出的判断:

Γ⊢e⇒A⇝t,Γ⊢e⇐A⇝t,

前者从表面项 e 综合类型 A 并产生核心项 t,后者在期望类型 A 下检查并产生 t。这复用双向类型检查的信息流,但依赖情形还要插入隐式参数、求 universe level、解 metavariable,并反复调用定义相等核对依赖余类型。可靠性要求 elaboration 成功后确有 Γ⊢t:A;若声明 surface 与 core 的擦除关系 |t|=e,还应证明生成项保留约定的运行含义。

应用规则可先综合 f:Π(x:A).B,检查实参得到 u:A,再输出 fu:B[u/x]。遇到隐式 Π 参数时,elaborator 生成 metavariable ?m:A,把 B[?m/x] 与后续期望类型产生约束;约束求解若给出替换 θ,最终核心项必须施加 θ 且不残留未解决必需洞。依赖模式匹配还需经 coverage 与 index unification 生成 eliminator 或 case tree,而不直接成为内核信任的任意分支语法。

完备性只能相对于明确的 surface 设计陈述:某些声明式可定型项需要用户注解、显式 motive 或搜索提示;高阶 unification 与实例搜索一般没有同时终止且完备的算法。Elaboration 失败也不等于核心命题不可证,只说明当前信息流和搜索策略未找到项。

直觉

表面语言允许人省略显然信息,核心语言要求机器把每个依赖关系写清。Elaborator 就像排版前的技术编辑:根据上下文补上宇宙层、隐式类型参数和强制运输,把模式子句重排成核心消去;最后由独立内核检查成稿,而不是要求用户信任编辑过程没有猜错。

依赖类型让这种补全具有反馈回路。选择一个隐式实参会改变后续参数的类型,后续显式实参又可能反过来确定先前的 metavariable。双向方向标记限制信息流,约束队列保存尚未确定的部分;二者共同把全局猜测缩小成可管理的局部求解,却不能消灭固有歧义。

显式向量连接与独立重检
例子与边界

设核心恒等函数为

id:Π{A:U0}.A→A.

表面项 id true 没写隐式 A。elaborator 先输出候选 id{?A}true,再由 true:Bool 产生约束 ?A≡Bool,求解后得到核心项

id{Bool}true:Bool.

若期望类型已经是 Bool,checking 方向可更早传播该信息。对

append:Π{m,n}.Vec(A,m)→Vec(A,n)→Vec(A,m+n),

调用 append xs ys 会从两个向量的类型恢复 m,n,再把结果类型实例化;这不是运行时读取长度,而是解 typing constraints。

边界例子是 λx. x 处在 synthesis 位置且没有注解:x 的类型无来源,系统可拒绝或要求 annotation,不能凭“显然是恒等函数”选择某一个宇宙与类型。另一个边界是方程 ?Fx≡t 的高阶求解;若允许任意 metavariable 函数,可能有多个不可比较解或进入不可判定搜索。实现常采用 pattern unification、occurs check、冻结约束与用户交互,这些是有意限定的完备范围,不应包装成一般依赖类型推断。

固定一个 Π/Nat/Vec 显式核心 ​

前面的例子说明一般 elaboration 的信息流;现在单独固定核心 K,把“最后交给内核”展开为可逐项检查的任务。宇宙层级 i,j∈N 是写在语法上的数,不含待求解的层级洞。规则为

Ui:Ui+1,Γ⊢A:UiΓ,x:A⊢B:UjΓ⊢Π(x:A).B:Umax(i,j).

此处没有宇宙累积:A:Ui 不自动成为 A:Ui+1,更没有 Ui:Ui。层级的后继与 Π 的最大值规则可对照标准宇宙层次。[2] 以下不是任何证明助理全部表面语法的复制。

核心项只含变量、已检查常量、宇宙、Π、带参数类型的 λ(x:A).t、应用,以及下列 Nat/Vec 原语;所有参数与层级均显式,没有 metavariable、隐式实参、模式子句或一般递归。局部语境依次检查,每个声明的类型只能使用其之前的变量。应用按 Π 消去把实参同时代入余类型,全部替换均避免捕获。

自然数和向量的形成、构造规则为

Nat:U0,zero:Nat,succ(n):Nat(n:Nat),Veci(A,n):Ui(A:Ui, n:Nat),nili(A):Veci(A,0),consi(A,n,a,v):Veci(A,succ(n))(a:A, v:Veci(A,n)).

0,1,2,… 只是 zero 与有限次 succ 的缩写。下标 i 防止含混地把同一个类型悄悄抬到不同宇宙。

消去子必须带齐 motive 和分支 ​

自然数消去子是全参数项构造,规则为

P:Π(k:Nat).Uj,z:P0,s:Π(k:Nat).Pk→P(succ(k)),n:NatnatElimj(P,z,s,n):Pn.

向量的 motive 还必须接收向量自身,而不只是长度。给定 A:Ui,规则为

P:Π(k:Nat).Veci(A,k)→Uj,z:P0(nili(A)),s:Π(k:Nat).Π(a:A).Π(xs:Veci(A,k)).Pkxs→P(succ(k))(consi(A,k,a,xs)),n:Nat,v:Veci(A,n)vecElimi,j(A,P,z,s,n,v):Pnv.

每行都是当前良构语境里的类型义务,不是 elaborator 可以宣称已解决的注释。尤其 Pnv 是 Uj 中的类型,而 P 的函数类型位于 Umax(i,j+1);自然数 motive 的函数类型也位于 Uj+1,不能把这两个层次混为一谈。

消去子的四条 ι-计算为

natElimj(P,z,s,0)≡z,natElimj(P,z,s,succ(k))≡sk(natElimj(P,z,s,k)),vecElimi,j(A,P,z,s,0,nili(A))≡z,vecElimi,j(A,P,z,s,succ(k),consi(A,k,a,xs))≡skaxs(vecElimi,j(A,P,z,s,k,xs)).

若被消去项是变量等中立项,消去子停住,不凭长度猜出其所有元素。Nat/Vec 的这组显式接口与实现方式可对照文献。[1] 该教程用 Type:Type 简化实现;本页改用上面的分层规则,不借该简化版本声称正规化或一致性。

把向量连接编译成完整消去项 ​

先把按第一个参数递归的加法声明为已检查的透明常量:

add: Nat→Nat→Nat,add:= λ(m:Nat).λ(n:Nat).natElim0(λ(k:Nat).Nat, n, λ(k:Nat).λ(r:Nat).succ(r), m).

检查 motive 得到 Nat→U0,基分支为 n:Nat,后继分支为 Nat→Nat→Nat。ι-规则于是给出

add0n≡n,add(succ(k))n≡succ(addkn).

对每个固定层级 i,目标连接函数类型为

appendi: Π(A:Ui).Π(m:Nat).Π(xs:Veci(A,m)).Π(n:Nat).Veci(A,n)→Veci(A,addmn).

在语境 A:Ui 中,写出三个仅用于展示的缩写;内核收到的是将它们展开后的项,不把它们当作新增公理:

MA:= λ(k:Nat).λ(v:Veci(A,k)).Π(n:Nat).Veci(A,n)→Veci(A,addkn),zA:= λ(n:Nat).λ(ys:Veci(A,n)).ys,sA:= λ(k:Nat).λ(a:A).λ(xs:Veci(A,k)).λ(ih:MAkxs).λ(n:Nat).λ(ys:Veci(A,n)).consi(A,addkn,a,ihnys).

完整定义为

appendi:=λ(A:Ui).λ(m:Nat).λ(xs:Veci(A,m)).vecElimi,i(A,MA,zA,sA,m,xs).

消去结果是一个仍接受 n,ys 的函数,因此这一项恰有上述 Π 类型。两个分支的检查具体如下:

  • 基分支要求 MA0(nili(A))。展开 motive 和 add 0 n 后得到 Πn.Veci(A,n)→Veci(A,n),正由 zA 居留。
  • 后继分支里 ihnys:Veci(A,addkn)。cons 的显式长度参数必须是 addkn,输出长度为其后继。期望的长度是 add(succ(k))n,由加法计算转为同一后继,所以 conversion 成功。

这也解释了为什么按第一个向量消去时,选按第一个参数计算的加法最直接。把规格机械换成 addnm,开放的后继分支一般不会靠当前计算规则变成同一类型;加法可交换的命题证明不是本核心自动执行的 conversion。

独立核检查实际检查哪些东西 ​

核心保存有限、有顺序的透明声明表 c:A:=t。每项只可引用先前已检查常量;先确认 A 是某宇宙中的类型,再检查 t:A,通过后才加入声明表。这排除前向引用和自引用;递归计算只来自上述消去子。局部语境也按同样的前缀良构原则检查。

以下是全注解核心的检查接口。Sort(A) 综合 A 的类型,并要求其弱头归约结果为某个 Ui,返回 i。Check(t,A) 先确认 A 为类型(父规则已建立的良构性可复用),再综合 t 的类型 B,调用 Conv(B,A)。综合规则逐个覆盖所有项构造:

核心项 必查前提与综合结果
变量/常量 查已检查语境/声明表,未知名字拒绝
Ui 输出 Ui+1
Π(x:A).B i=Sort(A),在 x:A 下求 j=Sort(B);输出 Umax(i,j)
λ(x:A).t 检查 A 为类型;在 x:A 下综合 t:B,输出 Π(x:A).B
fu 综合 f,把所得类型化到 Π(x:A).B;检查 u:A 后输出 B[u/x]
Nat / zero / succ 分别输出 U0 / Nat / Nat;succ 先检查实参为 Nat
Veci(A,n) 检查 A:Ui,n:Nat,输出 Ui
nili(A) 检查 A:Ui,输出 Veci(A,0)
consi(A,n,a,v) 检查 A:Ui,n:Nat,a:A,v:Veci(A,n);输出 Veci(A,succ(n))
两种消去子 依前述规则检查所有类型参数、motive、分支和被消去项,再输出 Pn 或 Pnv

Conv 只使用 λ 的 β、上述 ι 和已检查透明常量的 δ 展开,连同同余与 α 改名。不给函数 η、等式反映或任意数学定律开后门。一个直接实现把两侧递归归约到正规形,按绑定器的 α 等价比较。若得到同一个正规形,两条有限归约链就见证定义相等,conversion 有合法依据;层级标号也必须相同。

为了使这个教学检查器对任意输入都能返回,可给整个检查调用一个有限工作预算,每次递归处理语法节点和归约步骤都消耗预算;不足时返回 unknown,绝不返回 accept。这给出一个可能不完备的、终止的接受检查器,不需要把“所有原始语法都正规化”当成前提。更高效且完备的转换判定依赖相应类型理论的正规化、构造器可辨识性等元理论;正规化定理的输入首先是良构类型和良型项。[3]

接受可靠性按检查调用归纳即可定位每项责任:变量取已检查声明;Π 与 λ 使用良构扩张语境;应用由两个子推导和无捕获代入得到结果类型;Nat/Vec 构造与消去逐条使用上面的规则;conversion 成功时使用实际有限归约所见证的判断相等。声明表再按顺序归纳。因此每个接受结果都有核心推导,未知或拒绝都不产生可使用的声明。这是对所写算法的纸面论证;实际解析器、代入和归约实现仍在可信基中,不能称为已经机械化验证的软件。

两个向量的接受与两个错误候选 ​

取 A=Nat,i=0,完整输入为

xs=cons0(Nat,1,0,cons0(Nat,0,1,nil0(Nat))),ys=cons0(Nat,0,2,nil0(Nat)).

构造器规则分别综合 xs:Vec0(Nat,2) 和 ys:Vec0(Nat,1)。表面调用 append xs ys 可以推断出这三个隐式参数;内核实际收到

append0Nat2xs1ys.

依次应用 Π 消去,结果为 Vec0(Nat,add21)。两次后继归约再一次零归约给出

add21≡succ(add11)≡succ(succ(add01))≡3.

向量消去则先递归到空的左向量,返回 ys,再依次补回元素 1 与 0;最终正规项为

cons0(Nat,2,0,cons0(Nat,1,1,cons0(Nat,0,2,nil0(Nat)))),

确有类型 Vec0(Nat,3)。元素顺序与每个显式长度参数都可直接检查。

若 elaborator 错把第一个长度填成 1,内核会要求 xs:Vec0(Nat,1),但综合得到长度2,正规形不同,拒绝。若错误的连接定义把后继分支写成只返回 ih n ys、省去 cons,分支实际长度为 addkn,期望长度为其后继;对中立 k,前者是停住的消去表达式,后者的头为 succ,也不能转换。这一错误在泛型定义入库时就被拒绝,无须等到某个闭合测试。

独立重检意味着内核不采用 elaborator 声称的长度、motive 正确性或“约束已解”标记,而是从显式项和已检查声明重新推导。若两个阶段共用同一个有缺陷的代入器,仍可能一起出错;阶段分离缩小信任范围,却不是形式验证的替代品。

推论与应用

Elaboration 把丰富语法与小可信内核分离。notation、type class、coercion、record projection 和 tactic 可以在不扩张核心证明规则的情况下加入,只要最终生成的项通过检查。这种架构还改善错误定位:约束记录了哪一个隐式选择导致类型冲突,编辑器可展示尚未解决洞的局部语境与目标。

分离也明确了信任边界。Elaborator 有 bug 时可能拒绝合法程序或生成错误项,但健全内核会拦下后者;若编译器绕过重检、让未解 metavariable 成为任意常量,可靠性便失效。性能优化如缓存规范形、增量约束求解和启发式实例搜索都必须保持最终 core judgment 不变。

参考资料
  • Ulf Norell, Towards a Practical Programming Language Based on Dependent Type Theory, PhD thesis, Chalmers University of Technology, 2007,metavariables、dependent pattern matching 与 Agda elaboration。

  • Jana Dunfield and Neel Krishnaswami, “Bidirectional Typing,” ACM Computing Surveys 54(5), 2021, Article 98,双向规则、注解与 elaboration 的可靠性/完备性框架。

  • The Agda Team, Agda User Manual, “Implicit Arguments,” “Metavariables,” and “Coverage Checking” sections,现代依赖语言的 surface-to-core 机制,accessed 2026。

  • [1] Andres Löh, Conor McBride, Wouter Swierstra, “A Tutorial Implementation of a Dependently Typed Lambda Calculus”, Fundamenta Informaticae 102(2), 2010, §§4.1–4.2:Nat/Vec消去子和显式连接;§5明确指出其 Type:Type 简化的限制。

  • [2] Agda Team, Sort System,univSort、funSort、piSort:层级后继与Π形成的最大值规则。本页只取固定自然数层级的片段。

  • [3] Carlo Angiuli and Daniel Gratzer, Principles of Dependent Type Theory,课程草稿,§§3.2.1–3.2.2:良构输入上的正规化接口与类型检查。此处有预算检查器的接受证明不等于引用该书得到一个现成实现正确性定理。

关系图谱8 个相邻概念 · 2 类关系

拖动节点调整位置。

显示关系

显示:依赖

  1. 前置三跳
  2. 前置二跳
  3. 前置一跳
  4. 当前条目
  5. 后续一跳
  6. 后续二跳
  7. 后续三跳
文字版关系按与当前条目的最短距离分组
类型化关系