可以直接阅读本章,也可以通过交互式目录和依赖图选择其他路线。

交互式目录 · 依赖图

讨论的舞台是建立在累积层级 $V$ 之上的可构造宇宙。排中律在这里作为显式假设出现:整个模块由一个参数 lem 给出,它对层级 ℓ-suc ℓ 上的每个命题作出判定。本章需要的正是这一个层级,下文的所有构造都可以使用这一固定判定;对于其他层级上的命题,除已证明的定理所述内容外,不作任何论断。

module L.Choice.FiniteStageOrders {ℓ : Level} (lem : LEM (ℓ-suc ℓ)) where

本章证明每个以数码为索引的层都是有穷的,并以最先分歧赋予其良序;随后结合层号与局部序来良序化极限层。

先前的选择构造为一个族的每一格定位了该格首次拥有元素的层,并证明了它是一个后继。于是该格中恰在那里现身的每个元素,都是同一个集合的可定义子集:一个写在单一层之上的名字。尚缺的是比较这些名字的办法,而本章要在塔的底部造出的正是这种比较。

本章依赖两个论断。第一,凡以数码为索引的层都是有穷的,其确切含义见下文:它附带一份有穷的集合清单,清单包含它的全部元素。第二,有穷层带有一个良序:比较两个元素时,看它们最先在何处出现分歧,并把较大的位置判给含有该处的那一个。

第二个论断才是数学内容所在,它本质上是关于有穷集合的论断。若把同一构造用于自然数的子集,就会出现无穷下降:先是全体自然数,然后是从一开始的全体,再是从二开始的全体,如此下去,每一步删去尚存者中最先的那一个,因而严格落到更低处。构造本身并不排除这种情形;在有穷基底上,只有有穷多个子集,因此寻找最小元素的过程会终止。下文据此证明良基性:一份有穷清单加上一个线序,可以为任何非空性质给出最小元素,方法是逐项检查清单,并在每一步保留截至该处最小的候选;而「每个非空性质都有最小元素」在经典意义下就是良基性。

有穷性能沿塔逐层推广,是因为有穷集合的可定义子集就是它的全部子集,而带清单的集合只有有穷多个子集,清单上的每个位向量对应其中一个。于是一层的清单给出下一层的清单,递归便足以推进整个构造。

极限层的构造无须假设或证明各有穷层序之间相容。它先比较元素首次出现的层号;层号相同,才使用该层自己的序。因此,不同层的元素由层号比较,同一层首次出现的元素由局部序比较。

下文使用的名称都来自可构造层级:塔的层 Lset α、产生一层的全部可定义子集的算子 𝒟ₒ,以及数码 # n 是序数这一事实 numeral-ord。于是每个有限层 Lset (# n) 都是真正的层,这正是后续各节的递归能沿数码攀爬的原因。这里还引入了 Lset-suc 与 FinOf 相关工具,它们把一层与其内部的有穷集合联系起来。

比较需要一个满足三分律的基底序。自然数上的序 natOrder 是一个严格强良基的线序,打包为 SWO,其三情形比较 Tri 分为 lt、eq、gt 三种。后面各节的搜索程序都针对这一接口编写,因此适用于任何 SWO;而自然数的实例正是用来给数码排序的那一个。

module SemV = FOL.Semantics 𝒮ᵥ

open import Cubical.Data.Bool using ( false≢true )

布尔值在这里作为掩码出现:要枚举带点名册的集合的子集,就把每个条目保留或丢弃,用 Bool 上的 true 或 false 记录,而 false≢true 保证二者可区分。在索引一侧,自然数用严格序 _<_ 比较,它是传递且良基的,由 ¬m<m 排除自环,并可用 _≟_ 判定相等。这些恰好是找出见证某性质的最小下标所需的性质,也是扫描中作出逐步判定所需的性质。

open import Cubical.Data.Nat.Order using ( _<_; <-trans; ¬m<m; <-wellfounded; _≟_ )
import Cubical.Data.Nat.Order as NatOrder

这里的良基性由可达性谓词 Acc 表达:一个点是可达的,当且仅当它的每个前驱都可达,由构造子 acc 封装。当关系的一切点都可达时,称它具有 WellFounded 类型。Acc 上的证明义务都是命题,这一事实由 isPropAcc 记录,并在从「仅仅存在」的数据消去到可达性陈述时用到。模块 WFI 提供消费良基关系的递归原理。

open import Cubical.Induction.WellFounded
  using ( Acc; acc; WellFounded; isPropAcc; module WFI )

对累积层级中的集合 x,⟪ x ⟫ 是它的小呈现类型,⟪ x ⟫↪ 把该类型嵌入层级。等价 ∈∈ₛ 联系呈现中的成员关系与层级成员关系,∈-asFiber 则从成员关系证明恢复索引及其等同路径。空集给出第零层,冯·诺伊曼数码 # n 及其极限 ω 用来索引诸有穷层与极限。

open import Cubical.HITs.CumulativeHierarchy.Properties
  using ( ∈∈ₛ; ∈-asFiber; ⟪_⟫; ⟪_⟫↪ )
open import Cubical.HITs.CumulativeHierarchy.Constructions
  using ( ∅; ∅-empty; module InfinitySet )

下文的成员关系陈述取命题为值。因此,⟨ x ∈ˢ A ⟩ 是 x 属于 A 的证据类型;点名册用这一形式证明每个列出项确实属于集合,并陈述每个元素都被表示。

open InfinitySet using ( #_; ω )

open hPropView 𝒮ᵥ

点名册

Tally 用一个有穷索引族呈现集合的每个元素,允许重复,也不要求单射性或可判定相等。

有穷性以点名册的形式引入:取一个数和一个由相应多个集合组成的族,族中的每个集合都属于 A,并要求「A 的每个元素都仅仅等于其中某一个」。onto 表示该族列出了 A 的所有元素。

重复与不可判定的相等都不造成困难。扫描可以再次遇到同一元素,位向量也按位置记录取舍,即使两个位置名指同一集合亦然。因此,这种刻意保持较弱的有穷性概念能在下一层的构造中保持下去。

集合 A 的点名册有三个数据字段:数 size 决定列出的条目数,item 把每个合法位置 (即 Fin size 的元素) 映为集合 item i,而字段 inside 证明每个被列出的条目确实属于 A。没有这一条,更长的清单会平凡地覆盖较小的集合。注意同一元素完全可能出现在多个位置上:record 并不禁止这一点,也没有任何字段询问两个位置上的集合是否相同。

record Tally (A : S) : Type (ℓ-suc ℓ) where
  field
    size   : ℕ
    item   : Fin size → S
    inside : (i : Fin size) → ⟨ item i ∈ˢ A ⟩

第四个字段陈述覆盖性。给定 x 及其属于 A 的证明,onto 返回「索引 i 配路径 item i ≡ x」的命题截断。因此,只能得到某个索引仅仅存在,而不会暴露一个选定位置。后文只在目标为命题时消去这份截断见证。

    onto   : (x : S) → ⟨ x ∈ˢ A ⟩ → ∥ Σ[ i ∶ Fin size ] (item i ≡ x) ∥₁

劈开一个有穷索引

splitFin 与 joinFin 把小于和数的索引与某个加数中的索引对应起来,提供枚举掩码所需的算术。

为幂集清点需要枚举位向量,而长度为 n + 1 的向量个数是长度为 n 的两倍。因此需要一项索引算术:小于 a + b 的索引对应于小于 a 的索引或小于 b 的索引,反之亦然。后文只使用其中一个方向,所以这里只证明该方向;bumpLeft 是使沿 a 的递归通过类型检查所需的移位。

一个具体的图景有帮助。取 a = 2、b = 3,小于 5 的索引恰好等于「小于 2 的索引或小于 3 的索引」:joinFin 把左加数放进前两个位置、把右加数放进后三个位置,而 splitFin 询问一个索引落入哪个区域。这里与重复无关,因为这两张图关心的是位置,而不是日后放在位置上的条目。

第一张图处理左端增加一的和。bumpLeft 取一个属于 a 或 b 的索引,给出一个属于 suc a 或 b 的索引:左边的索引被外推一格,右边的原样保留。它本身没有内容,存在的原因只是 splitFin 的递归步会从左加数剥掉一格,需要一个移位把左索引放回正确的类型。注意 joinFin 只给出了从 Fin a ⊎ Fin b 到 Fin (a + b) 的方向,且 a 显式给出,以便递归能对它作模式匹配。

bumpLeft : {a b : ℕ} → Fin a ⊎ Fin b → Fin (suc a) ⊎ Fin b
bumpLeft (inl i) = inl (suc i)
bumpLeft (inr j) = inr j

joinFin : (a : ℕ) {b : ℕ} → Fin a ⊎ Fin b → Fin (a + b)
joinFin 0    (inr j)       = j

joinFin 与 splitFin 形状上互为逆映射,不过后文只证明一个方向的往返。joinFin 沿 a 递归:a 为零时,小于 0 + b 的索引就是小于 b 的索引;a 为后继时,第一个位置属于左加数,于是位置为零的左索引映到零号位置,其余一律上移一格。splitFin 沿同一递归倒着走:小于 a + b 的索引先问它是否小于 a,后继情形用 bumpLeft 恢复被剥掉的类型。

joinFin (suc a) (inl zero)    = zero
joinFin (suc a) (inl (suc i)) = suc (joinFin a (inl i))
joinFin (suc a) (inr j)       = suc (joinFin a (inr j))

splitFin : (a : ℕ) {b : ℕ} → Fin (a + b) → Fin a ⊎ Fin b
splitFin 0    j       = inr j

往返 split-join 说的是:对刚刚拼合的索引再作劈分,就回到原来的左或右索引。每条子句要么是 refl,要么是对递归路径施用 cong:splitFin (joinFin x) 的计算已经归约到对递归答案施加 bumpLeft,而 cong bumpLeft 把归纳假设穿过这一移位。相反的复合从未被断言,这里也没有任何关于「拼合是单射」的主张。

splitFin (suc a) zero    = inl zero
splitFin (suc a) (suc i) = bumpLeft (splitFin a i)

split-join : (a : ℕ) {b : ℕ} (x : Fin a ⊎ Fin b) → splitFin a (joinFin a x) ≡ x
split-join 0    (inr j)       = refl
split-join (suc a) (inl zero)    = refl

这一算术给掩码一节带来的是对规模的精确记账。当长度 suc n 的掩码枚举在 maskCount n 处把索引一分为二时,splitFin 判定首位是 false 还是 true,并把剩下的索引交给 n 处的递归;mask-onto 与 split-join 合起来证明每个位向量都被触及。

split-join (suc a) (inl (suc i)) = cong bumpLeft (split-join a (inl i))
split-join (suc a) (inr j)       = cong bumpLeft (split-join a (inr j))

枚举掩码

maskAt 枚举固定长度的全部布尔向量,而 mask-onto 证明每种选取模式都会出现。

长度为 n 的掩码是一个 n 位向量;对已经清点的集合,它指明保留哪些条目。共有 maskCount n 个掩码,即二的 n 次幂,这里写成反复加倍的形式。maskAt 把索引解释为掩码:按索引属于两个加数中的哪一支确定首位,再由该支中的剩余索引确定尾部。每个掩码都由某个索引得到,这就是 mask-onto,也是后文使用该枚举所需的唯一性质;该枚举不要求逐点单射。

取 n = 2,四个索引给出从 false ∷ false ∷ [] 到 true ∷ true ∷ [] 的四个掩码。这个构造事实上无重复地枚举它们;不过后面的点名册论证只使用已证明的覆盖性 mask-onto,并不依赖单射性。

掩码的计数按「将来枚举它的那个递归」来定义:长度为零恰有一个掩码;长度为 suc n 的掩码是一个首位加上一个长度为 n 的掩码,故计数为 maskCount n + maskCount n。这就是写成反复加倍形式的二的 n 次幂,而两个加数相同,恰好正是 splitFin 所期待的形状。

maskCount : ℕ → ℕ
maskCount 0    = 1
maskCount (suc n) = maskCount n + maskCount n

maskCons : (n : ℕ) → (Fin (maskCount n) → Vec Bool n)
         → Fin (maskCount n) ⊎ Fin (maskCount n) → Vec Bool (suc n)

maskCons 把一个首位接到从索引相应半支读出的尾部上:左加数取 false,右加数取 true。于是 maskAt 把索引读成掩码:长度为零时唯一的掩码是空向量;长度为 suc n 时,小于 maskCount (suc n) = maskCount n + maskCount n 的索引被一分为二,所在的半支给出首位,内层索引给出尾部。这个读法是一个定义而非定理:它只是按规则计算。

maskCons n r (inl j) = false ∷ r j
maskCons n r (inr j) = true  ∷ r j

maskAt : (n : ℕ) → Fin (maskCount n) → Vec Bool n
maskAt 0    j = []
maskAt (suc n) j = maskCons n (maskAt n) (splitFin (maskCount n) j)

覆盖性是 mask-onto 的内容,而它有意不带截断:给定一个向量 v,该陈述产生一个真实的索引,连同从该索引读出的掩码到 v 的路径。基情形中,空向量来自第零号索引。这是整个枚举中唯一必须交付数据而非仅仅存在性的地方,而它之所以能做到,是因为递归沿着向量本身进行。

mask-onto : (n : ℕ) (v : Vec Bool n) → Σ[ j ∶ Fin (maskCount n) ] (maskAt n j ≡ v)
mask-onto 0    []          = zero , refl
mask-onto (suc n) (false ∷ v) =
  joinFin (maskCount n) (inl (mask-onto n v .fst))
  , (cong (maskCons n (maskAt n)) (split-join (maskCount n) (inl (mask-onto n v .fst)))

后继情形由向量决定分支。首位为 false 时,尾部的索引经 joinFin 拼入左半支;路径分两步拼装:先用 split-join 证明对拼合索引的劈分确实还原出左半支,再用 cong (false ∷_) 把递归得到的路径带上首位。true 的情形逐字相同,只是换成右半支。结合计数,这说明已清点集合的掩码被 Fin (maskCount size) 覆盖,恰好是 Tally 字段所期待的形状。

     ∙ cong (false ∷_) (mask-onto n v .snd))
mask-onto (suc n) (true ∷ v)  =
  joinFin (maskCount n) (inr (mask-onto n v .fst))
  , (cong (maskCons n (maskAt n)) (split-join (maskCount n) (inr (mask-onto n v .fst)))
     ∙ cong (true ∷_) (mask-onto n v .snd))

选出一个子族

select 按布尔掩码筛选一个有穷族,其成员关系引理则把选中的条目与标为真的位置对应起来。

select 把掩码作用到一个族上:它保留那些位为 true 的条目,并把它们重新组成一个族,同时给出该族的长度。长度是由递归产生的,这正是关键:无须计数,也不需要任何算术把答案与掩码联系起来。

两条规格说明各自刻画结果包含什么,且都不带截断,因为二者都是同一次递归的直接推论。marks 的方向相反:它把对诸条目的一次判定变成记录该判定的掩码。

一个小例子显示了与重复的交互。取一个含重复条目的族,并取保留两个副本的掩码:选出的子族便两次含有该条目,两条副本各由引理以各自的原始位置回答。没有任何东西被丢失或合并,因为从来没有任何东西被要求唯一。

辅助函数 selectStep 完成筛选的一步:给定条目 x 与已选好的族,它把 x 排在最前并报告新长度 suc k。其结果类型把族与长度打包成一个依赖对,于是递归可以增长长度而不必对掩码做任何算术。

selectStep : {ℓ' : Level} {X : Type ℓ'} → X → Σ[ k ∶ ℕ ] (Fin k → X)
           → Σ[ k ∶ ℕ ] (Fin k → X)
selectStep {X = X} x (k , g) = suc k , h
  where
  h : Fin (suc k) → X

select 是沿掩码的递归。空掩码什么也不选,用荒谬模式表达:长度为零的族没有任何位置。首位为 false 时丢弃头部并沿右移后的族递归;首位为 true 时用 selectStep 保留头部。每一步族都右移一格,这正是全篇出现的 λ i → f (suc i) 所记录的内容。

  h zero    = x
  h (suc i) = g i

select : {ℓ' : Level} {X : Type ℓ'} (n : ℕ) → (Fin n → X) → Vec Bool n
       → Σ[ k ∶ ℕ ] (Fin k → X)
select 0    f v           = zero , λ ()

第一条规格 select-out 顺向读出选取结果:被选族的每个位置 j 都来自某个位为 true 的原始位置 i,且该处的条目确实是原来的条目 f i。这一主张是数据而非仅仅的存在性:实际产生一个见证 i,位与等式都显式给出。

select (suc n) f (false ∷ v) = select n (λ i → f (suc i)) v
select (suc n) f (true ∷ v)  = selectStep (f zero) (select n (λ i → f (suc i)) v)

select-out : {ℓ' : Level} {X : Type ℓ'} (n : ℕ) (f : Fin n → X) (v : Vec Bool n)
             (j : Fin (select n f v .fst))
           → Σ[ i ∶ Fin n ] ((lookup i v ≡ true) × (select n f v .snd j ≡ f i))

证明沿与定义相同的递归走。false 情形中头部已被丢弃,于是在尾部回答 j 的原始位置要上移成整向量中的 suc i;局部的 step 恰好完成对见证三元组的这一簿记。

select-out 0    f []          ()
select-out (suc n) f (false ∷ v) j       = step (select-out n (λ i → f (suc i)) v j)
  where
  step : Σ[ i ∶ Fin n ] ((lookup i v ≡ true)
           × (select n (λ i → f (suc i)) v .snd j ≡ f (suc i)))

true 情形分两个子情形。若被选位置是第一个,答案就是头部本身,两条等式都因 select 把头部原封不动作为零号位置返回而由 refl 成立;否则递归回答尾部的位置,同样的上移照旧适用。

       → Σ[ i ∶ Fin (suc n) ] ((lookup i (false ∷ v) ≡ true)
           × (select (suc n) f (false ∷ v) .snd j ≡ f i))
  step (i , e , q) = suc i , (e , q)
select-out (suc n) f (true ∷ v)  zero    = zero , (refl , refl)
select-out (suc n) f (true ∷ v)  (suc j) = step (select-out n (λ i → f (suc i)) v j)

第二个子情形重复同样的上移簿记,只是此时头部仍在:true ∷ v 的被选族是头部接上尾部的选取结果,因此头部之后的位置在尾部得到回答并映回 suc i。两个分支只在这一重定位上不同,这正是它们各自需要一个 step 的原因。

  where
  step : Σ[ i ∶ Fin n ] ((lookup i v ≡ true)
           × (select n (λ i → f (suc i)) v .snd j ≡ f (suc i)))
       → Σ[ i ∶ Fin (suc n) ] ((lookup i (true ∷ v) ≡ true)
           × (select (suc n) f (true ∷ v) .snd (suc j) ≡ f i))

反向规格 select-in 说每个被标记的条目都被选中:位为 true 的原始位置 i 拥有一个被选位置 j,其条目为 f i。同样,这一主张是显式的数据,即一个真实的 j 连同一条路径。两个方向都不带截断,这正是后文关于成员关系的论证能在选取两侧传递真实见证的原因。

  step (i , e , q) = suc i , (e , q)

select-in : {ℓ' : Level} {X : Type ℓ'} (n : ℕ) (f : Fin n → X) (v : Vec Bool n)
            (i : Fin n) → lookup i v ≡ true
          → Σ[ j ∶ Fin (select n f v .fst) ] (select n f v .snd j ≡ f i)
select-in 0    f []          ()      e

其证明从另一端映照同一递归。空族中的位置是荒谬的。false 情形中头部不可能被标为真,故假设 e 与 false≢true 矛盾;右移后的位置照旧递归。true 情形中头部以零号位置作答,更深的位置照旧递归。

select-in (suc n) f (false ∷ v) zero    e = ⊥₀-rec (false≢true e)
select-in (suc n) f (false ∷ v) (suc i) e = select-in n (λ i → f (suc i)) v i e
select-in (suc n) f (true ∷ v)  zero    e = zero , refl
select-in (suc n) f (true ∷ v)  (suc i) e = step (select-in n (λ i → f (suc i)) v i e)
  where

最后一条子句完成前置的簿记:尾部找到的位置变成现在头部在前的新族中的 suc j,条目等式原样保留。两条规格合起来说明选取结果既不比掩码标出的多、也不比它少,尽管没有断言这两种位置对应方式互为逆映射。

  step : Σ[ j ∶ Fin (select n (λ i → f (suc i)) v .fst) ]
           (select n (λ i → f (suc i)) v .snd j ≡ f (suc i))
       → Σ[ j ∶ Fin (select (suc n) f (true ∷ v) .fst) ]
           (select (suc n) f (true ∷ v) .snd j ≡ f (suc i))
  step (j , q) = suc j , q

marks 把筛选反过来用:它不读掩码来保留条目,而是取一个关于条目的布尔函数 d,并写下记录其输出的掩码,每个位置一位。基情形是空向量,递归步在头部询问 d 并沿右移后的族继续。

marks : {ℓ' : Level} {X : Type ℓ'} (n : ℕ) → (Fin n → X) → (X → Bool) → Vec Bool n
marks 0    f d = []
marks (suc n) f d = d (f zero) ∷ marks n (λ i → f (suc i)) d

marks-lookup : {ℓ' : Level} {X : Type ℓ'} (n : ℕ) (f : Fin n → X) (d : X → Bool)
               (i : Fin n) → lookup i (marks n f d) ≡ d (f i)

marks-lookup 证明记录下的掩码确实在每个位置重现 d 给出的布尔值:在 marks n f d 的位置 i 处查询得到 d (f i)。头部情形由 marks 的计算规则得 refl,更深的位置照旧递归。有了这条引理,后面的 maskOf 才能证明它写下的掩码重现给定的子集。

marks-lookup (suc n) f d zero    = refl
marks-lookup (suc n) f d (suc i) = marks-lookup n (λ i → f (suc i)) d i

把一个真值判定成一位

排中律把每个命题化为掩码所用的布尔位,而两条规格从该位分别读回真与假。

排中律给出的是一次判定,而掩码需要的是一位,故须把二者衔接起来。判定作为实参显式传入,而不是在定义内部求解:正是这一点使两条来回引理能靠对它作模式匹配来证明;真值本身也显式给出,使来回规格以预期命题为参数。

这一转换是排中律在点名册构造中的一个具体用途:判定一条成员关系命题,再把答案记录为一位。

decideOf 把判定变成一位:yes 携带 ⟨ P ⟩ 的证明,记为 true;no 携带反驳,记为 false。命题 P 本身与计算无关,被匹配的只是判定,因此这个定义是一对方程而非证明。

decideOf : (P : hProp (ℓ-suc ℓ)) → Dec ⟨ P ⟩ → Bool
decideOf P (yes _) = true
decideOf P (no _) = false

decide-true : (P : hProp (ℓ-suc ℓ)) (s : Dec ⟨ P ⟩) → ⟨ P ⟩ → decideOf P s ≡ true
decide-true P (yes _) p = refl

两条往返把位接回真值。decide-true 说 ⟨ P ⟩ 的证明迫使该位为 true:在反驳支中这个证明本身会被反驳,那正是矛盾。decide-sound 反向读出:位为 true 便给出 ⟨ P ⟩ 的证明,或直接取自左支,或因右支会迫使 false ≡ true 而得。合起来,它们说明对于传入的那个判定,该位忠实地回答 ⟨ P ⟩ 是否成立。

decide-true P (no np) p = ⊥₀-rec (np p)

decide-sound : (P : hProp (ℓ-suc ℓ)) (s : Dec ⟨ P ⟩) → decideOf P s ≡ true → ⟨ P ⟩
decide-sound P (yes p) _ = p
decide-sound P (no _) e = ⊥₀-rec (false≢true e)

已清点层的可定义子集

有穷性经由本节沿塔逐级传递。固定序数 σ 和层 Lset σ 的一份点名册,目标是给出 𝒟ₒ (Lset σ) (该层可定义子集的全体) 的点名册。已知点名册的每个条目都是该层的元素,因而在该层的小元素类型中有相应的元素;掩码指明保留哪些元素,part 把保留的元素张成有穷集。按基本公理一章的 finSet∈𝒟ₒ,这样张成的集合是该层的可定义子集,由「等于这些条目之一」的有穷析取定义。反过来,该层的任何可定义子集 x 也能被恢复:按每个点名册条目是否属于 x 的可判定成员关系加以标记,该掩码张成的集合恰是 x,其中包含关系 𝒟ₒ∋⊆ 保证 x 的每个元素本就被点名册列出。于是 maskCount size 个掩码仅仅覆盖全部可定义子集,而这正是 Tally 所要求的。

Lset σ 的元素作为集合处在该层中,但 finSet 需要小元素类型 ⟪ Lset σ ⟫ 中的名字;嵌入 ⟪ Lset σ ⟫↪ 把这种名字读成集合。成员关系呈现为截断原像,不过这个嵌入的原像取值为命题,所以 ∈-asFiber 可以消去截断,返回一个显式名字及其等同于 item i 的路径。index i 与 index-eq i 正是该原像元素的两个投影。这并非从任意点名册原像中选取索引,因为允许重复的点名册原像未必是命题。

module PowerStep (σ : S) (oσ : IsOrd σ) (t : Tally (Lset σ)) where
open Tally t
open FinOf σ oσ using ( finSet∈𝒟ₒ )

index : Fin size → ⟪ Lset σ ⟫
index i = ∈-asFiber {a = item i} {b = Lset σ} (inside i) .fst

同一纤维的第二个分量是路径 index-eq i,它记录嵌入元素经一条路径而非定义等式回到 item i。此后在集合 item i 与元素 index i 之间的每一次转换都要沿这条路径用传输完成。元素就位后,掩码 v 被转换为一次选取:chosen v 给出一个长度,连同恰好列出被选元素的函数,这由此前的 select 构造。

index-eq : (i : Fin size) → ⟪ Lset σ ⟫↪ (index i) ≡ item i
index-eq i = ∈-asFiber {a = item i} {b = Lset σ} (inside i) .snd

chosen : Vec Bool size → Σ[ k ∶ ℕ ] (Fin k → ⟪ Lset σ ⟫)
chosen v = select size index v

part : Vec Bool size → S

part 就是张成的集合:它把每个被选元素经嵌入读出,并取所得结果的有穷集,落在集合类型 S 中。由于 Lset σ 元素的有穷族张成该层的可定义子集,part-def 直接由 finSet∈𝒟ₒ 得到证书 ⟨ part v ∈ˢ 𝒟ₒ (Lset σ) ⟩,无须额外工作。第一条规格从反方向读成员关系:若 y 属于 part v,则仅仅存在某个点名册位置,其位为 true 且其条目等于 y。

part v = finSet (chosen v .fst) (λ j → ⟪ Lset σ ⟫↪ (chosen v .snd j))

part-def : (v : Vec Bool size) → ⟨ part v ∈ˢ 𝒟ₒ (Lset σ) ⟩
part-def v = finSet∈𝒟ₒ (chosen v .fst) (chosen v .snd)

part-out : (v : Vec Bool size) (y : S) → ⟨ y ∈ˢ part v ⟩
         → ∥ Σ[ i ∶ Fin size ] ((lookup i v ≡ true) × (item i ≡ y)) ∥₁

证明分两步复合。第一步,finSet-out 解开有穷张成集中的成员关系:它仅仅给出选取中的一个位置 j,使嵌入元素等于 y。第二步,select-out 把该位置追回到完整点名册中的来源,恢复索引 i,满足 lookup i v ≡ true 以及 chosen v .snd j ≡ index i。两步的数据都在截断之内产生,因此没有从单纯存在性命题中提取选定的见证。

part-out v y y∈ = map₁ step
  (finSet-out (chosen v .fst) (λ j → ⟪ Lset σ ⟫↪ (chosen v .snd j)) y y∈)
  where
  step : Σ[ j ∶ Fin (chosen v .fst) ] (⟪ Lset σ ⟫↪ (chosen v .snd j) ≡ y)
       → Σ[ i ∶ Fin size ] ((lookup i v ≡ true) × (item i ≡ y))

最后所需等式的方向是 item i ≡ y。先沿 sym (index-eq i) 从 item i 到嵌入后的名字 index i。随后 select-out 给出 chosen v .snd j ≡ index i,取其对称并施加嵌入,便到达选中的嵌入名字。最后,有限集成员关系给出的路径 q 到达 y。三者的复合正是证明中显示的三段路径。

  step (j , q) = out .fst
               , ( out .snd .fst
                 , (sym (index-eq (out .fst))
                    ∙ cong ⟪ Lset σ ⟫↪ (sym (out .snd .snd)) ∙ q) )
    where

相反的规格正向运行。若位置 i 处的位为 true,则条目 item i 确实属于 part v。原因在于选取中确实含有该元素:select-in 对每个被标记的位置,在被选族中找到一个持有同一元素的槽位,随后 finSet-in 证明其嵌入形式的成员关系。

    out : Σ[ i ∶ Fin size ] ((lookup i v ≡ true) × (chosen v .snd j ≡ index i))
    out = select-out size index v j

part-mem : (v : Vec Bool size) (i : Fin size) → lookup i v ≡ true
         → ⟨ item i ∈ˢ part v ⟩
part-mem v i e = subst (λ w → ⟨ w ∈ˢ part v ⟩) path

由于张成集中的成员关系是针对嵌入元素陈述的,而目标针对条目 item i,两者要靠下文的路径 path 连接,并用 subst 沿该路径搬移成员关系证书。辅助的 ins 保存 select-in 给出的槽位:被选族中的一个位置,其条目等于 index i。

  (finSet-in (chosen v .fst) (λ j → ⟪ Lset σ ⟫↪ (chosen v .snd j))
    (⟪ Lset σ ⟫↪ (chosen v .snd (ins .fst))) ∣ ins .fst , refl ∣₁)
  where
  ins : Σ[ j ∶ Fin (chosen v .fst) ] (chosen v .snd j ≡ index i)
  ins = select-in size index v i e

余下的路径 path 把槽位的等式与 index-eq i 拼接,因此传输后的成员关系正是 item i 的成员关系。两个方向就位后,构造可以反向运行。maskOf 对任意集合 x 给出判定掩码:对每个点名册条目判定它是否属于 x;排中律 lem 给出判定,decideOf 再把它变成一位。目标 part-mask 陈述:对该层中的可定义子集 x,此掩码张成的集合就是 x 本身。

  path : ⟪ Lset σ ⟫↪ (chosen v .snd (ins .fst)) ≡ item i
  path = cong ⟪ Lset σ ⟫↪ (ins .snd) ∙ index-eq i

maskOf : S → Vec Bool size
maskOf x = marks size item
  (λ y → decideOf (y ∈ˢ x) (SemV.decideMembership lem y x))

part-mask : (x : S) → ⟨ x ∈ˢ 𝒟ₒ (Lset σ) ⟩ → part (maskOf x) ≡ x

层次中集合的成员关系是命题,因此外延性 extensionalV 把所断言的等式 part (maskOf x) ≡ x 归约为逐点的成员关系等价;⇔toPath 把两个方向组装成路径。正向表明张成集的每个元素都属于 x。

part-mask x x∈ = extensionalV (λ y → ⇔toPath (fwd y) (bwd y))
  where
  fwd : (y : S) → ⟨ y ∈ˢ part (maskOf x) ⟩ → ⟨ y ∈ˢ x ⟩
  fwd y y∈ = rec₁ ((y ∈ˢ x) .snd) step (part-out (maskOf x) y y∈)
    where

正向的前提本身就是单纯的存在性:某个被标记的位置,其条目等于 y。由于目标 ⟨ y ∈ˢ x ⟩ 是命题,截断可以消去到其中。记录的见证是位置 i,其位为 true 且条目为 y;由于该位正是通过判定这个条目是否属于 x 算出的,用 decide-sound 把位读回即得 item i 属于 x,再用等式 item i ≡ y 把它传输给 y。

    step : Σ[ i ∶ Fin size ] ((lookup i (maskOf x) ≡ true) × (item i ≡ y))
         → ⟨ y ∈ˢ x ⟩
    step (i , e , q) = subst (λ w → ⟨ w ∈ˢ x ⟩) q
      (decide-sound (item i ∈ˢ x) (SemV.decideMembership lem (item i) x)
        (sym (marks-lookup size item

反向从 y 属于 x 出发,须产生张成集中的成员关系。由于该目标又是命题,其截断的前提可以消去。这里的前提来自点名册的覆盖:x 是该层的可定义子集,而 𝒟ₒ∋⊆ 说 Lset σ 的可定义子集的每个元素都是 Lset σ 自身的元素,因此点名册的 onto 单纯地把 y 列为某个条目 item i。

               (λ z → decideOf (z ∈ˢ x) (SemV.decideMembership lem z x)) i) ∙ e))
  bwd : (y : S) → ⟨ y ∈ˢ x ⟩ → ⟨ y ∈ˢ part (maskOf x) ⟩
  bwd y y∈x = rec₁ ((y ∈ˢ part (maskOf x)) .snd) step
    (onto y (𝒟ₒ∋⊆ (Lset σ) x x∈ y y∈x))
    where

给定等于 y 的条目 i,只需证明 item i 属于张成集,并沿 item i ≡ y 传输。由 part-mem,成员关系需要位置 i 的位为 true。它确实如此:掩码记录了 item i ∈ˢ x 的判定,而由于 y 属于 x,路径 item i ≡ y 把该证明传输过来,decide-true 便迫使该位为 true。

    step : Σ[ i ∶ Fin size ] (item i ≡ y) → ⟨ y ∈ˢ part (maskOf x) ⟩
    step (i , q) = subst (λ w → ⟨ w ∈ˢ part (maskOf x) ⟩) q
      (part-mem (maskOf x) i
        (marks-lookup size item
           (λ z → decideOf (z ∈ˢ x) (SemV.decideMembership lem z x)) i
         ∙ decide-true (item i ∈ˢ x)
           (SemV.decideMembership lem (item i) x)

part-mask 的两个方向就此组装完毕,本节的关键成果随之而来。由于每个掩码都经 mask-onto 来自某个索引,掩码 (允许重复、单纯地) 枚举了 Lset σ 的全部可定义子集。它们共有 maskCount size 个,因此 powerTally 记录一个该大小的点名册,其在索引 j 处的条目是掩码 maskAt size j 张成的集合。其余字段补全记录:每个条目附带其可定义性证书,覆盖条款随后给出。

             (subst (λ w → ⟨ w ∈ˢ x ⟩) (sym q) y∈x)))

powerTally : Tally (𝒟ₒ (Lset σ))
powerTally = record
  { size   = maskCount size
  ; item   = λ j → part (maskAt size j)

记录的 inside 字段在每个被枚举的掩码处复用证书 part-def,因此 powerTally 的每个条目确实是该层的可定义子集。剩下检查 onto,即截断的覆盖性。给定 Lset σ 的任意可定义子集 x,必须单纯地给出一个索引,其被枚举的条目等于 x。

  ; inside = λ j → part-def (maskAt size j)
  ; onto   = cover }
  where
  cover : (x : S) → ⟨ x ∈ˢ 𝒟ₒ (Lset σ) ⟩
        → ∥ Σ[ j ∶ Fin (maskCount size) ] (part (maskAt size j) ≡ x) ∥₁

见证索引是 mask-onto 为判定掩码 maskOf x 产生的那个。该索引处被枚举的条目是 part (maskAt size j),沿所得路径改写掩码后它等于 part (maskOf x),随后 part-mask 把它与 x 等同。整个命题落在截断之中,这正是 Tally 的覆盖性所要求的:每个可定义子集都被命中,尽管未必由唯一的掩码命中。

  cover x x∈ = ∣ mask-onto size (maskOf x) .fst
               , (cong part (mask-onto size (maskOf x) .snd) ∙ part-mask x x∈) ∣₁

最小元与良基性

本节花用的是先前造好的点名册,而非再造一份。固定一个类型及其上一个三歧、非自反且传递的关系,即严格良序所要求的一切,只差良基。过程 scan 走过一个有穷族,并且不带任何截断地返回:要么是一个满足谓词、且在满足者之中最小的条目,要么是「没有条目满足它」的反驳。它是沿长度的普通递归:每一步由外部供给的判定过程判定谓词在头部是否成立,再由三歧比较头部与迄今为止的最佳者。因此扫描本身只相对于逐点可判定性作构造,不再向任意谓词索取排中律。在「该族单纯覆盖整个类型」的假设下,Search.Over.least 把它升级为任一仅仅非空的可判定谓词的最小元。后面的良基性证明才从本章的经典假设取得它所需的特定判定过程。

固定 A 上满足三歧、非自反与传递的严格关系 ≺。目标是从有穷覆盖族推出良基性,而不是把良基性作为假设。对谓词 P,Least P m 同时记录 m 满足 P,以及每个严格更小的满足者都会导出矛盾。

module Search {A : Type (ℓ-suc ℓ)} (_≺_ : A → A → Type (ℓ-suc ℓ))
              (tri : (a b : A) → Tri (a ≺ b) (a ≡ b) (b ≺ a))
              (irr : (a : A) → a ≺ a → ⊥₀)
              (trans : (a b c : A) → a ≺ b → b ≺ c → a ≺ c) where
Least : (P : A → hProp (ℓ-suc ℓ)) → A → Type (ℓ-suc ℓ)

扫描的输出类型 Found P n f 是两个显式选项的析取。左支中,某个位置 i 持有一个满足 P 的条目,且族内没有其他满足 P 的条目位于其下。右支中,每个条目都不满足谓词。两个选项携带的都是完整数据而非截断的存在性,这使后续构造能返回真实的元素。

Least P m = ⟨ P m ⟩ × ((b : A) → ⟨ P b ⟩ → b ≺ m → ⊥₀)

Found : (P : A → hProp (ℓ-suc ℓ)) (n : ℕ) (f : Fin n → A) → Type (ℓ-suc ℓ)
Found P n f =
  (Σ[ i ∶ Fin n ] (⟨ P (f i) ⟩ × ((j : Fin n) → ⟨ P (f j) ⟩ → f j ≺ f i → ⊥₀)))
  ⊎ ((i : Fin n) → ⟨ P (f i) ⟩ → ⊥₀)

scan 沿族长度递归定义。空族空虚地返回右支。对有头部的族,递归先处理尾部,把位置整体后移一位;外部供给的 P 在头部的判定交给 combine,它把尾部的结果与头部的判定合并成整个族的结果。

scan : (P : A → hProp (ℓ-suc ℓ))
     → ((a : A) → Dec ⟨ P a ⟩)
     → (n : ℕ) (f : Fin n → A) → Found P n f
scan P decP 0    f = inr (λ ())
scan P decP (suc n) f = combine (scan P decP n (λ i → f (suc i))) (decP (f zero))
  where
  combine : Found P n (λ i → f (suc i))

combine 的第一支处理尾部已经给出最小满足者 f (suc i)、而头部也满足谓词的情形。此时两个候选竞争,三歧判定 f zero 与 f (suc i) 哪个更小;辅助函数 decide 分析该比较的三种结果。

          → Dec ⟨ P (f zero) ⟩ → Found P (suc n) f
  combine (inl (i , pi , mi)) (yes p₀) = decide (tri (f zero) (f (suc i)))
    where
    decide : Tri (f zero ≺ f (suc i)) (f zero ≡ f (suc i)) (f (suc i) ≺ f zero)
           → Found P (suc n) f

若头部严格小于尾部的优胜者,头部便成为新的优胜者。其最小性逐位置核验:在头部自身处,断言 f zero ≺ f zero 直接与非自反性矛盾;在尾部各位置,传递性把 f j ≺ f zero ≺ f (suc i) 连成链,交给尾部已确立的最小性 mi。

    decide (lt h) = inl (zero , (p₀ , minAt))
      where
      minAt : (j : Fin (suc n)) → ⟨ P (f j) ⟩ → f j ≺ f zero → ⊥₀
      minAt zero    pj hj = irr (f zero) hj
      minAt (suc j) pj hj = mi j pj (trans (f (suc j)) (f zero) (f (suc i)) hj h)

若头部等于尾部当前的最小候选,该候选仍为最小。假设头部低于候选,沿二者的等式传输后就得到候选低于自身,与非自反性矛盾;尾部位置仍由 mi 处理。

    decide (eq h) = inl (suc i , (pi , minAt))
      where
      minAt : (j : Fin (suc n)) → ⟨ P (f j) ⟩ → f j ≺ f (suc i) → ⊥₀
      minAt zero    pj hj = irr (f (suc i)) (subst (λ w → w ≺ f (suc i)) h hj)
      minAt (suc j) pj hj = mi j pj hj

若尾部的优胜者严格小于头部,它得以保留。此时优胜者之下的假设性条目有两条出路:经头部 f (suc i) ≺ f zero ≺ f (suc i) 的传递性给出一个自比较,由非自反性驳倒;而尾部自身的各位置交给 mi。优胜者的证书在每个分支都由旧证书重建。

    decide (gt h) = inl (suc i , (pi , minAt))
      where
      minAt : (j : Fin (suc n)) → ⟨ P (f j) ⟩ → f j ≺ f (suc i) → ⊥₀
      minAt zero    pj hj = irr (f (suc i)) (trans (f (suc i)) (f zero) (f (suc i)) h hj)
      minAt (suc j) pj hj = mi j pj hj

第二支在头部不满足谓词时保留尾部的优胜者。这里完全不需要比较:头部既然不满足 P,便无从挑战优胜者,因此头部处的假想反例直接由判定 n₀ 驳倒,尾部各位置依旧交给 mi。

  combine (inl (i , pi , mi)) (no n₀) = inl (suc i , (pi , minAt))
    where
    minAt : (j : Fin (suc n)) → ⟨ P (f j) ⟩ → f j ≺ f (suc i) → ⊥₀
    minAt zero    pj hj = ⊥₀-rec (n₀ pj)
    minAt (suc j) pj hj = mi j pj hj

对称地,当尾部全无满足者而头部确实满足谓词时,头部就是新的优胜者。其最小性立即可得:头部自身由非自反性处理,任何满足谓词的尾部位置都与尾部的反驳 none 矛盾。

  combine (inr none) (yes p₀) = inl (zero , (p₀ , minAt))
    where
    minAt : (j : Fin (suc n)) → ⟨ P (f j) ⟩ → f j ≺ f zero → ⊥₀
    minAt zero    pj hj = irr (f zero) hj
    minAt (suc j) pj hj = ⊥₀-rec (none j pj)

最后一支是一致情形:尾部与头部都给不出满足者,于是报告整个族中无人满足。反驳逐位置组装:头部交给 n₀,每个尾部位置交给 none。至此,导言所说的四种组合齐备。

  combine (inr none) (no n₀) = inr atAll
    where
    atAll : (i : Fin (suc n)) → ⟨ P (f i) ⟩ → ⊥₀
    atAll zero    p = n₀ p
    atAll (suc i) p = none i p

子模块 Over 添加了把有穷族变成点名册所需的那条前提:cov 说 A 的每个元素都被该族单纯命中,这是允许重复的截断覆盖。在此前提下,least 把扫描的答案升级为整个类型上的最小元:其输入只是一个「某元素满足 P」的截断见证,其输出则是显式数据,即一个元素连同 Least P m。

module Over (n : ℕ) (f : Fin n → A)
            (cov : (a : A) → ∥ Σ[ i ∶ Fin n ] (f i ≡ a) ∥₁) where
least : (P : A → hProp (ℓ-suc ℓ)) → ((a : A) → Dec ⟨ P a ⟩)
      → ∥ Σ[ a ∶ A ] ⟨ P a ⟩ ∥₁ → Σ[ m ∶ A ] Least P m
least P decP h = decide (scan P decP n f)
  where

在 least 内部,辅助函数 nowhere 处理扫描的「无满足者」分支:假定没有条目满足 P,就必须驳倒给定的截断见证。该消去是合法的,因为目标是空类型这一命题,因此见证的截断可以在不作任何选择的情况下拆开。

  nowhere : ((i : Fin n) → ⟨ P (f i) ⟩ → ⊥₀) → ⊥₀
  nowhere none = rec₁ isProp⊥ atWitness h
    where
    atWitness : Σ[ a ∶ A ] ⟨ P a ⟩ → ⊥₀
    atWitness (a , pa) = rec₁ isProp⊥

具体而言,见证给出元素 a 及 ⟨ P a ⟩,覆盖 cov a 单纯地指出族中位置 i 满足 f i ≡ a;由于目标仍是命题,该纤维可以被读出。把 ⟨ P a ⟩ 的证明沿 f i ≡ a 反向传输得到 ⟨ P (f i) ⟩,假定的反驳 none 便将其化为矛盾。紧接的代码行执行的正是这次传输。

      (λ { (i , q) → none i (subst (λ w → ⟨ P w ⟩) (sym q) pa) }) (cov a)
  decide : Found P n f → Σ[ m ∶ A ] Least P m
  decide (inl (i , pi , mi)) = f i , (pi , everywhere)
    where
    everywhere : (b : A) → ⟨ P b ⟩ → b ≺ f i → ⊥₀

前面预告的传输在这里执行,且两个分量同时进行。给定整个类型中位于优胜者之下的假想条目 b,附有 ⟨ P b ⟩ 与 b ≺ f i,覆盖单纯地给出满足 f j ≡ b 的族位置 j;把满足性与比较性都沿该路径反向传输,优胜者的族级证书 mi 便把二者一并驳倒。于是扫描仅剩的分支,即反驳 none,彻底矛盾,因为见证已被证明必然把一个满足者带进族中。

    everywhere b pb hb = rec₁ isProp⊥
      (λ { (j , q) → mi j (subst (λ w → ⟨ P w ⟩) (sym q) pb)
                          (subst (λ w → w ≺ f i) (sym q) hb) }) (cov b)
  decide (inr none) = ⊥₀-rec (nowhere none)

wellFounded : WellFounded _≺_

为证明良基性,先判定任意 a 是否可及;肯定支直接返回证书。否定支用有穷扫描找出可及性被反驳的最小元素 m。若 m 的每个前驱都可及,acc below 就证明 m 可及;把 found 中保存的 m 的反驳作用于这份证书即得矛盾。最初关于 a 的反驳只用于证明「不可及」这一谓词非空。

wellFounded a = fromDec (lem (Acc _≺_ a , isPropAcc a))
  where
  fromDec : Dec (Acc _≺_ a) → Acc _≺_ a
  fromDec (yes h) = h
  fromDec (no nh) = ⊥₀-rec (found .snd .fst (acc below))

被取最小的性质是 NotAcc,即不可及性。其底层陈述是一个否定,而否定是命题,故 NotAcc 是合法的真值 Ω,least 可以作用于它。输入是 a 与假定反驳 nh 的截断配对,因此该假设只是说不可及元素之集非空。

    where
    NotAcc : A → hProp (ℓ-suc ℓ)
    NotAcc b = (Acc _≺_ b → ⊥₀) , isProp→ isProp⊥
    found : Σ[ m ∶ A ] Least NotAcc m
    found = least NotAcc (λ b → lem (NotAcc b)) ∣ a , nh ∣₁

设 m 是刚求得的最小不可及元素。要证它可及,须证每个前驱 b 可及,而 b 的可及性又是命题,故再次由排中律判定;辅助函数 pick 在肯定支中返回证书。

    below : (b : A) → b ≺ found .fst → Acc _≺_ b
    below b hb = pick (lem (Acc _≺_ b , isPropAcc b))
      where
      pick : Dec (Acc _≺_ b) → Acc _≺_ b
      pick (yes h) = h

在否定支中,b 将是严格小于最小不可及元素 m 的不可及元素,而 Least NotAcc m 的最小性条款恰好驳斥这一点。于是每个前驱皆可及,证书 acc below 合法,把它交给假定的可及性反驳便封闭了矛盾。注意:全程并未构造或排除任何无穷下降序列,论证完全就是这个矛盾。

      pick (no nb) = ⊥₀-rec (found .snd .snd b nb hb)

最先的分歧

本节定义有穷层将要携带的序。固定一个集合 A 与集合之上的一个关系 R,后者读作 A 的诸元素上的一个序。A 的两个子集,按它们在何处分歧来比较。「x 先于 y」的见证,是 A 的一个元素 z,它属于 y 而不属于 x,且 x 与 y 在 z 之下一致,意即 A 中被 R 排在 z 之前的每个元素,属于其中之一当且仅当属于另一个。倒过来读:z 就是最先的分歧点,而它属于 y。关系 precedes R A 是这类见证的截断存在;非自反性立刻成立,且完全不需要任何前提:x 对自己的见证会既属于 x 又不属于 x。后文证明在关于基底序的前提下得到三歧与传递,并用有穷性得到良基性。

两个成分分别陈述。Agrees R A x y z 说:对 A 中被 R 排在 z 之前的每个元素 w,属于 x 与属于 y 双向重合。Witness R A x y z 随后组装完整见证:z 属于 A,属于 y,不属于 x,且其下方一致成立。正是元素条款的方向决定了比较中哪一方胜出。

Agrees : (R : S → S → hProp (ℓ-suc ℓ)) (A x y z : S) → Type (ℓ-suc ℓ)
Agrees R A x y z = (w : S) → ⟨ w ∈ˢ A ⟩ → ⟨ R w z ⟩
                 → (⟨ w ∈ˢ x ⟩ → ⟨ w ∈ˢ y ⟩) × (⟨ w ∈ˢ y ⟩ → ⟨ w ∈ˢ x ⟩)

Witness : (R : S → S → hProp (ℓ-suc ℓ)) (A x y z : S) → Type (ℓ-suc ℓ)
Witness R A x y z =

precedes R A x y 是「这类见证单纯存在」的命题,随 squash₁ 打包成一个真值。由于见证藏在截断之后,被断言的只有其存在,任何东西都不选定 z。非自反性于是只花一行:把截断消去到空类型 (一个命题) 中,暴露出同时有 z ∈ x 与 z ∉ x 的见证,把第二条施于第一条即是矛盾。

  ⟨ z ∈ˢ A ⟩ × ⟨ z ∈ˢ y ⟩ × (⟨ z ∈ˢ x ⟩ → ⊥₀) × Agrees R A x y z

precedes : (R : S → S → hProp (ℓ-suc ℓ)) (A : S) → S → S → hProp (ℓ-suc ℓ)
precedes R A x y = ∥ Σ[ z ∶ S ] Witness R A x y z ∥₁ , squash₁

precedes-irrefl : (R : S → S → hProp (ℓ-suc ℓ)) (A x : S) → ⟨ precedes R A x x ⟩ → ⊥₀
precedes-irrefl R A x = rec₁ isProp⊥ (λ { (z , _ , z∈ , z∉ , _) → z∉ z∈ })

最先分歧序的传递性与三歧确实需要关于基底序的前提,而二者所需不同,故一并收进一个模块。其参数是 R 在 A 诸元素上的三歧与传递,以及 R 在那些元素上的最小元原则;在塔中,这些都来自下面那一层。

传递性是两个见证之间的比较。若 x 在 p 处先于 y,y 在 q 处先于 z,则 p 与 q 不可能相等,因为 p 属于 y 而 q 不属于;而二者中较小的那个就见证了 x 先于 z。两支要核对的是同样的两件事:较小的那一点方向正确,以及它之下的一致性可以复合。

该模块收集最先分歧序将要继承的三条前提。baseTri 与 baseTrans 说 R 限制在 A 的元素上时三歧且传递,baseLeast 是 A 上的最小元原则:从「A 元素的某个性质单纯非空」出发,它给出一个满足该性质、且没有更小的 A 元素也满足的元素。注意结论的形状:它是显式数据而非截断,因为调用方需要真实的极小元。

module Difference (R : S → S → hProp (ℓ-suc ℓ)) (A : S)
  (baseTri : (a b : S) → ⟨ a ∈ˢ A ⟩ → ⟨ b ∈ˢ A ⟩ → Tri ⟨ R a b ⟩ (a ≡ b) ⟨ R b a ⟩)
  (baseTrans : (a b c : S) → ⟨ R a b ⟩ → ⟨ R b c ⟩ → ⟨ R a c ⟩)
  (baseLeast : (P : S → hProp (ℓ-suc ℓ)) → ∥ Σ[ a ∶ S ] (⟨ a ∈ˢ A ⟩ × ⟨ P a ⟩) ∥₁
             → Σ[ m ∶ S ] (⟨ m ∈ˢ A ⟩ × ⟨ P m ⟩
                 × ((b : S) → ⟨ b ∈ˢ A ⟩ → ⟨ P b ⟩ → ⟨ R b m ⟩ → ⊥₀)))
  where

传递性的陈述恰好按 precedes 的产出形式取两条前提:x ≺ y 与 y ≺ z 的截断见证,并返回 x ≺ z 的截断见证。因此证明先消去第一个截断,再消去第二个,二者的目标都又是截断、因而是命题。

precedes-trans : (x y z : S) → ⟨ precedes R A x y ⟩ → ⟨ precedes R A y z ⟩
               → ⟨ precedes R A x z ⟩
precedes-trans x y z hxy hyz =

两个见证都暴露后,both 接收完整数据:见证 x 先于 y 的点 p 及其元素条款 agp,以及见证 y 先于 z 的点 q 及其 agq。两个基底点的比较交给基底三歧,辅助函数 decide 分析其三种结果。

  rec₁ squash₁ (λ wp → rec₁ squash₁ (both wp) hyz) hxy
  where
  both : Σ[ p ∶ S ] Witness R A x y p → Σ[ q ∶ S ] Witness R A y z q
       → ⟨ precedes R A x z ⟩
  both (p , p∈A , p∈y , p∉x , agp) (q , q∈A , q∈z , q∉y , agq) =

若 p 严格小于 q,它继续充当 x 先于 z 的见证。它自身的条款原封不动,因为它们只涉及 x 与 y;须核实的是 p 属于 z,以及 p 之下 x 与 z 的一致性。p 属于 z 由 agq 在点 p 处给出,它把 p 对 y 的成员关系沿复合比较传输过去。

    decide (baseTri p q p∈A q∈A)
    where
    decide : Tri ⟨ R p q ⟩ (p ≡ q) ⟨ R q p ⟩ → ⟨ precedes R A x z ⟩
    decide (lt h) = ∣ p , (p∈A , (agq p p∈A h .fst p∈y , (p∉x , ag))) ∣₁
      where

p 之下的一致性逐条款复合。要证 w ∈ x 蕴含 w ∈ z:agp 把 w ∈ x 提升为 w ∈ y,再用 agq 把对 y 的元素提升到 z,其中用基底传递性保证 w 也位于 q 之下。反向条款对称,把 z 降到 y 再降到 x。相等情形不可能出现:p 属于 y 而 q 不属于,沿路径 p ≡ q 传输成员关系即得矛盾。

      ag : Agrees R A x z p
      ag w w∈A hw =
          (λ wx → agq w w∈A (baseTrans w p q hw h) .fst (agp w w∈A hw .fst wx))
        , (λ wz → agp w w∈A hw .snd (agq w w∈A (baseTrans w p q hw h) .snd wz))
    decide (eq h) = ⊥₀-rec (q∉y (subst (λ v → ⟨ v ∈ˢ y ⟩) h p∈y))

若改为 q 严格小于 p,角色对调:由 q 见证 x 先于 z。它关于 y 与 z 的条款照旧,但须确立对 x 的元素与一致性。关于元素,在点 q 处读 agp,把 q 对 x 的元素传输为对 y 的元素,与 q ∉ y 矛盾;辅助函数 q∉x 把这一反驳打包。

    decide (gt h) = ∣ q , (q∈A , (q∈z , (q∉x , ag))) ∣₁
      where
      q∉x : ⟨ q ∈ˢ x ⟩ → ⊥₀
      q∉x qx = q∉y (agp q q∈A h .fst qx)
      ag : Agrees R A x z q

q 之下的一致性以镜像顺序复合:先用 agp 借助 q ≺ p 的基底传递性把 w 置于 p 之下,从而把对 x 的元素下推到 y,agq 再把它上提到 z;反向条款先把 z 降到 y,再降到 x。两个不对称情形都已处理、相等已被驳倒,传递性就此完成。

      ag w w∈A hw =
          (λ wx → agq w w∈A hw .fst (agp w w∈A (baseTrans w q p hw h) .fst wx))
        , (λ wz → agp w w∈A (baseTrans w q p hw h) .snd (agq w w∈A hw .snd wz))

三歧正是使用排中律与最小元原则的地方。先问这两个子集在 A 中是否有分歧之处。若没有,则它们在 A 中处处一致;又因二者都不超出 A,故它们本就处处一致,外延性把它们认同。若有,则存在一个最先的分歧点;再作一次判定,即该点是否属于第一个子集,就知道比较朝哪个方向走。该点之下的一致性在两支中都自动成立:按该点的选法,它之下无一处分歧。

排中律在 agree 内部第二次被使用,用来把「没有分歧」变成「一致」;这一步恰是一次双重否定的消去。

这里的陈述并不把 A 的两个子集 x、y 当作可定义性证书,而是当作普通集合,并附上二者都不超出 A 的前提。结论是一个 Tri,即本章通用的三分判断:x 先于 y、作为集合相等,或 y 先于 x。证明先对 Some 使用排中律发问;Some 将被构造成一个命题,即一条截断的存在陈述,因此可以把 squash₁ 作为其命题性证书交给 lem。

precedes-tri : (x y : S) → ((w : S) → ⟨ w ∈ˢ x ⟩ → ⟨ w ∈ˢ A ⟩)
                         → ((w : S) → ⟨ w ∈ˢ y ⟩ → ⟨ w ∈ˢ A ⟩)
             → Tri ⟨ precedes R A x y ⟩ (x ≡ y) ⟨ precedes R A y x ⟩
precedes-tri x y x⊆ y⊆ = decide (lem (Some , squash₁))
  where

两条截断组织了这个问题。谓词 Apart w 仅仅说 w 区分了这两个子集,方向不限:它属于其一而不属于另一。截断类型 Some 仅仅说 A 的某个元素是分歧点。二者都配以 squash₁,因而都是命题而非数据;这正是可以用排中律判定它们、随后又能把 Some 的反驳消去成矛盾的依据。

  Apart : S → hProp (ℓ-suc ℓ)
  Apart w = ∥ (⟨ w ∈ˢ x ⟩ × (⟨ w ∈ˢ y ⟩ → ⊥₀))
            ⊎ ((⟨ w ∈ˢ x ⟩ → ⊥₀) × ⟨ w ∈ˢ y ⟩) ∥₁ , squash₁
  Some : Type (ℓ-suc ℓ)
  Some = ∥ Σ[ a ∶ S ] (⟨ a ∈ˢ A ⟩ × ⟨ Apart a ⟩) ∥₁

辅助引理 agree 把「无分歧」转成「一致」,一次一个方向。前提 na 反驳 Apart w,结论是 w 处元素等价的两条包含子句。证明只有这里需要从否定性陈述造出元素蕴含,而它实际上是化了装的双重否定消去。

  agree : (w : S) → (⟨ Apart w ⟩ → ⊥₀)
        → (⟨ w ∈ˢ x ⟩ → ⟨ w ∈ˢ y ⟩) × (⟨ w ∈ˢ y ⟩ → ⟨ w ∈ˢ x ⟩)
  agree w na = fwd , bwd
    where
    fwd : ⟨ w ∈ˢ x ⟩ → ⟨ w ∈ˢ y ⟩

前向子句:设 w ∈ˢ x,对 w ∈ˢ y 用排中律发问。若成立即完成。若得到反驳 nh,那么 w 其实是分歧点,左析取支 wx , nh 就是见证;把该见证装入截断交给 na 便得矛盾,⊥*-rec 再从矛盾产出所需元素,这里就是缺失的成员关系证明。目标 ⊥* 是命题,故把截断的 Apart w 消去到它是正当的。

    fwd wx = pick (SemV.decideMembership lem w y)
      where
      pick : Dec ⟨ w ∈ˢ y ⟩ → ⟨ w ∈ˢ y ⟩
      pick (yes h) = h
      pick (no nh) = ⊥₀-rec (na ∣ inl (wx , nh) ∣₁)

后向子句是其镜像。设 w ∈ˢ y,排中律判定 w ∈ˢ x;若有反驳,则经右析取支 nh , wy 会使 w 成为分歧点,而 na 恰好反驳这一点。两条子句合起来说:若在 w 处不存在差异点,则属于 x 与属于 y 在 w 处重合。

    bwd : ⟨ w ∈ˢ y ⟩ → ⟨ w ∈ˢ x ⟩
    bwd wy = pick (SemV.decideMembership lem w x)
      where
      pick : Dec ⟨ w ∈ˢ x ⟩ → ⟨ w ∈ˢ x ⟩
      pick (yes h) = h

现在设 Some 被反驳,即 A 中没有分歧点。辅助引理 nApart 把这一点包装成对 Apart 的逐点反驳,same 将在每处 w 使用它来证明两集合相等。被反驳的见证落在 A 中这一前提在下一步了结,随后 agree 的元素等价即可在每点使用。

      pick (no nh) = ⊥₀-rec (na ∣ inr (nh , wy) ∣₁)
  same : (Some → ⊥₀) → x ≡ y
  same ns = extensionalV step
    where
    nApart : (w : S) → ⟨ Apart w ⟩ → ⊥₀

只要两个子集都落在 A 中,分歧点必属于 A。事实上,截断析取 ha 被消去到命题 w ∈ˢ A 中:左支成立时 w 属于 x,x⊆ 把它送进 A;右支成立时 y⊆ 同理。注意消去的方向:进入取值为命题的成员关系,这恰是命题截断所允许的。

    nApart w ha = ns ∣ w , (inA , ha) ∣₁
      where
      inA : ⟨ w ∈ˢ A ⟩
      inA = rec₁ ((w ∈ˢ A) .snd)
        (λ { (inl (wx , _)) → x⊆ w wx ; (inr (_ , wy)) → y⊆ w wy }) ha

在每处 w,agree w (nApart w) 的两条子句断言:属于 x 当且仅当属于 y。组合子 ⇔toPath 把这两个命题 w ∈ˢ x 与 w ∈ˢ y 之间的这份当且仅当提升为二者作为类型之间的路径,而这正是累积层级的外延性所消费的形式。把逐点路径交给 extensionalV 便得路径 x ≡ y,于是三歧的 eq 分支得证。

    step : (w : S) → (w ∈ˢ x) ≡ (w ∈ˢ y)
    step w = ⇔toPath (agree w (nApart w) .fst) (agree w (nApart w) .snd)
  decide : Dec Some
         → Tri ⟨ precedes R A x y ⟩ (x ≡ y) ⟨ precedes R A y x ⟩
  decide (no ns) = eq (same ns)

另一支中 Some 成立:A 的某个元素是分歧点。把最小元原则 baseLeast (对 A 的元素上的基底序 R 可用) 作用于谓词 Apart,它返回显式的记录 found,而非截断的存在陈述:一个点 m,在 A 中、分歧,且在 R 序之下其下方再无分歧点。正是这种显式性,使得最小分歧点此后能被用作见证。

  decide (yes hs) = side (SemV.decideMembership lem m x)
    where
    found : Σ[ m ∶ S ] (⟨ m ∈ˢ A ⟩ × ⟨ Apart m ⟩
              × ((b : S) → ⟨ b ∈ˢ A ⟩ → ⟨ Apart b ⟩ → ⟨ R b m ⟩ → ⊥₀))
    found = baseLeast Apart hs

found 的各分量被一次性拆开并命名:点 m、其在 A 中的成员关系 m∈A、分歧性 apartM、以及最小性 belowM。逐一命名使下面两个对称分支保持可读,因为每一支都要引用其中若干字段。

    m : S
    m = found .fst
    m∈A : ⟨ m ∈ˢ A ⟩
    m∈A = found .snd .fst
    apartM : ⟨ Apart m ⟩

最小性字段 belowM 反驳任何严格低于 m 的分歧点;这里把它改排为比较假设在末位的形式,以配合即将到来的用法。手握最小分歧点之后,排中律判定 m 是否属于 x,side 把每个答案化为三歧的一个分支。

    apartM = found .snd .snd .fst
    belowM : (w : S) → ⟨ w ∈ˢ A ⟩ → ⟨ R w m ⟩ → ⟨ Apart w ⟩ → ⊥₀
    belowM w w∈A hw ha = found .snd .snd .snd w w∈A ha hw
    side : Dec ⟨ m ∈ˢ x ⟩
         → Tri ⟨ precedes R A x y ⟩ (x ≡ y) ⟨ precedes R A y x ⟩

若 m 确实属于 x,则 m 见证 y 先于 x:它在第二个集合中而不在第一个中。子引理 m∉y 通过对截断的 apartM 作情形分析来反驳 m ∈ˢ y:左支中见证本身就带有对 m ∈ˢ y 的反驳;右支中对 m ∈ˢ x 的反驳与 mx 相抵触。消去截断是允许的,因为目标 ⊥* 是命题。

    side (yes mx) = gt ∣ m , (m∈A , (mx , (m∉y , ag))) ∣₁
      where
      m∉y : ⟨ m ∈ˢ y ⟩ → ⊥₀
      m∉y my = rec₁ isProp⊥
        (λ { (inl (_ , nmy)) → nmy my ; (inr (nmx , _)) → nmx mx }) apartM

m 之下的一致性也免费换边。对 m 之下的每个 w,belowM 反驳 Apart w,故 agree w 适用,给出双向的元素等价;这里只是把二元组按相反次序写出,把原本从 x 到 y 取向的一致性变成 Agrees R A y x m。与 m∈A、mx、m∉y 合起来,这是一份完整的 Witness,见证 y 先于 x,由 gt 装入截断交付。

      ag : Agrees R A y x m
      ag w w∈A hw = agree w (belowM w w∈A hw) .snd , agree w (belowM w w∈A hw) .fst
    side (no nmx) = lt ∣ m , (m∈A , (my , (nmx , ag))) ∣₁
      where
      my : ⟨ m ∈ˢ y ⟩

镜像的一支改设 m 不属于 x,产出 x 先于 y 的 lt 见证。从 apartM 提取 m ∈ˢ y 又是一次截断情形分析:左支会断言 m ∈ˢ x,被 nmx 反驳,故只有右支存活,而它直接带有该成员关系。这次 m 之下的一致性无须换向,因为见证的取向恰与 agree 的产出一致。两个对称分支齐备后,precedes 的三歧完成,一层上的局部序便是其元素上的线序,只待良基性。

      my = rec₁ ((m ∈ˢ y) .snd)
        (λ { (inl (mx , _)) → ⊥₀-rec (nmx mx) ; (inr (_ , h)) → h }) apartM
      ag : Agrees R A x y m
      ag w w∈A hw = agree w (belowM w w∈A hw)

有穷诸层

沿数码的递归把点名册与最先分歧良序从每个有穷层传到下一层。

以数码为索引的层正是有穷层,每层上的序由递归构造:第零层为空;n 的后继层上的序以层 n 自身的序为基础,并按最先分歧处比较层 n 的可定义子集。before-irrefl 在每层都成立且无需归纳,因为该比较的非自反性不需要前提,而第零层没有任何比较。

定义从三分判断的一件小工具开始。Tri-map 对 Tri 逐支作用:每个备选支各应用一个函数;三条子句就是它的计算规则。它将把「关于两个集合证明的三歧」转换为「关于一层的两个点所需的三歧」,二者只差是否附带成员关系证明。

Tri-map : {ℓ₁ ℓ₂ ℓ₃ ℓ₄ ℓ₅ ℓ₆ : Level}
          {A₁ : Type ℓ₁} {B₁ : Type ℓ₂} {C₁ : Type ℓ₃}
          {A₂ : Type ℓ₄} {B₂ : Type ℓ₅} {C₂ : Type ℓ₆}
        → (A₁ → A₂) → (B₁ → B₂) → (C₁ → C₂) → Tri A₁ B₁ C₁ → Tri A₂ B₂ C₂
Tri-map f g h (lt a) = lt (f a)

以数码为索引的层在此命名:finiteStage n 即层 Lset (# n)。关系 before 随后是对索引的递归。零处它取假真值,任何一对都不会被关系到。后继处它是对前一层使用 precedes:比较成员关系的基底集合就是层 n 本身,而寻找最先分歧所沿的基底序是 before n,即递归在下一层造出的那个序。

Tri-map f g h (eq b) = eq (g b)
Tri-map f g h (gt c) = gt (h c)

finiteStage : ℕ → S
finiteStage n = Lset (# n)

before : ℕ → S → S → hProp (ℓ-suc ℓ)

before 的非自反性在所有数码处成立,且证明不作归纳。零处前提是假命题的证明,由 ⊥*-rec 消去;后继处恰是 precedes-irrefl,即定义该比较时已确立的无前提非自反性。正因如此,非自反性不属于递归必须携带的数据。

before 0    x y = ⊥
before (suc n) = precedes (before n) (finiteStage n)

before-irrefl : (n : ℕ) (x : S) → ⟨ before n x x ⟩ → ⊥₀
before-irrefl 0    x h = ⊥*-rec h
before-irrefl (suc n) x h = precedes-irrefl (before n) (finiteStage n) x h

基例的空性单独记录为 zero-empty:没有集合是第零层的元素。从 Lset (# zero) 读出成员关系证书,仅仅给出某一层 δ,使 δ 属于数码零且 x 是 Lset δ 的可定义子集;数码零没有元素,∅-empty 把任何所谓的元素变成矛盾。由于目标 ⊥* 是命题,消去该截断是正当的。

zero-empty : (x : S) → ⟨ x ∈ˢ finiteStage zero ⟩ → ⊥₀
zero-empty x h = rec₁ isProp⊥ step (Lset-out (# zero) x h)
  where
  step : Σ[ δ ∶ S ] (⟨ δ ∈ˢ ∅ ⟩ × ⟨ x ∈ˢ 𝒟ₒ (Lset δ) ⟩) → ⊥₀
  step (δ , δ∈ , _) = ∅-empty δ (∈∈ₛ {a = δ} {b = ∅} .fst δ∈)

递归必须携带的数据只有一份对元素的清点、三歧与传递:非自反性在每层都自动成立,而良基性只在用到之处当场推出,不必随身携带。层的一个点是一个集合连同它的成员关系证明;由于成员关系是命题,两个点只要集合相等就相等。在「关于集合的陈述」与「载体必须是类型的那个束」之间往返时,需要做的全部工作就在于此。

前节的搜索机制作用在类型上,故层的一个元素被包装成 Point:一个集合连同它在 finiteStage n 中的成员关系证书。关系 Below 在底层集合处读取 before n。由于成员关系是命题,集合相同的两个点已然相等;这一个事实承担了「关于集合的陈述」与「关于点的陈述」之间往返的全部工作。

Point : ℕ → Type (ℓ-suc ℓ)
Point n = Σ[ x ∶ S ] ⟨ x ∈ˢ finiteStage n ⟩

Below : (n : ℕ) → Point n → Point n → Type (ℓ-suc ℓ)
Below n a b = ⟨ before n (a .fst) (b .fst) ⟩

record StageOrder (n : ℕ) : Type (ℓ-suc ℓ) where

层 n 的归纳恰好保留后继步所需的事实:finiteStage n 的点名册、before n 对该层元素的三歧性,以及 before n 对任意集合的传递性。非自反性由最先分歧统一推出;局部序需要良基性时,则从点名册重新得到。

  field
    tally : Tally (finiteStage n)
    tri   : (x y : S) → ⟨ x ∈ˢ finiteStage n ⟩ → ⟨ y ∈ˢ finiteStage n ⟩
          → Tri ⟨ before n x y ⟩ (x ≡ y) ⟨ before n y x ⟩
    trans : (x y z : S) → ⟨ before n x y ⟩ → ⟨ before n y z ⟩ → ⟨ before n x z ⟩

在 Ordered 内部,第一项任务是关于点的三歧。triPoint 把关于集合的三歧 tri 交给 Tri-map;中间一支的结论是路径,需要转换,Σ≡Prop 恰好提供这一点:由于第二分量是某个命题的证明,底层集合之间的路径可以延拓为点之间的路径。

module Ordered (n : ℕ) (r : StageOrder n) where
open StageOrder r public
open Tally tally

triPoint : (a b : Point n) → Tri (Below n a b) (a ≡ b) (Below n b a)
triPoint a b = Tri-map id (Σ≡Prop (λ z → (z ∈ˢ finiteStage n) .snd)) id

点名册由集合提升到点:把每个条目配上它自己的成员关系证明,得到 points。覆盖陈述 covers 随后是 onto 经这一配对的搬运:给定一个点,onto 仅仅提供一个索引,其条目具有相同的集合,Σ≡Prop 再把集合的等式升级为点的等式。覆盖仍然是截断的,与点名册本身一样。

  (tri (a .fst) (b .fst) (a .snd) (b .snd))

points : Fin size → Point n
points i = item i , inside i

covers : (a : Point n) → ∥ Σ[ i ∶ Fin size ] (points i ≡ a) ∥₁
covers a = map₁ (λ { (i , q) → i , Σ≡Prop (λ z → (z ∈ˢ finiteStage n) .snd) q })

在层的点上,三歧、非自反与传递同有穷点名册结合,给出两个结论:有穷扫描为每个仅仅非空的谓词找出最小点,而同一个最小反例论证给出点关系的良基性。

  (onto (a .fst) (a .snd))

open Search (Below n) triPoint (λ a → before-irrefl n (a .fst))
            (λ a b c → trans (a .fst) (b .fst) (c .fst)) public
open Over size points covers public

order : SWO (Point n)

这些事实确定了 finiteStage n 诸点上的严格良序:关系是 Before n,三条序律来自层比较,良基性则来自有穷扫描。因此,构造清楚地区分了局部比较与排除无穷下降的有穷性论证。

order = record
  { _<∙_   = Below n
  ; tri∙   = triPoint
  ; irr∙   = λ a → before-irrefl n (a .fst)
  ; trans∙ = λ a b c → trans (a .fst) (b .fst) (c .fst)

最后一条引理以下一层所需的形状包装最小元。leastMem 取集合上的一个谓词 P,它仅仅被该层的某个元素满足,并返回显式的满足 P 的元素 m,连同 before n 序下的最小性:该层中满足 P 的元素 b 没有严格低于 m 的。除前提外,这里没有任何截断。

  ; wf∙    = wellFounded }

leastMem : (P : S → hProp (ℓ-suc ℓ)) → ∥ Σ[ a ∶ S ] (⟨ a ∈ˢ finiteStage n ⟩ × ⟨ P a ⟩) ∥₁
         → Σ[ m ∶ S ] (⟨ m ∈ˢ finiteStage n ⟩ × ⟨ P m ⟩
             × ((b : S) → ⟨ b ∈ˢ finiteStage n ⟩ → ⟨ P b ⟩
                        → ⟨ before n b m ⟩ → ⊥₀))

证明在点的层面运行搜索并拆包结果。least 作用于提升后的谓词与重新包装的截断见证,返回显式的对:一个点 m 及其 Least 证书。随后把点的三个分量与证书的两个分量重新装配成集合层面的陈述,最小性子句由把证书作用于对 b , b∈ 而得。

leastMem P h = found .fst .fst
             , ( found .fst .snd
               , ( found .snd .fst
                 , (λ b b∈ pb hb → found .snd .snd (b , b∈) pb hb) ) )
  where

剩下的只是粘合:Q 在点的底层集合处读取集合层面的谓词,found 以从三元组重新包装成「一个点加一个证明」的截断前提调用 least。这条 leastMem 正是递归在后继层作为 baseLeast 喂给 Difference 的东西,它把搜索机制与最先分歧序之间的环节闭合。

  Q : Point n → hProp (ℓ-suc ℓ)
  Q a = P (a .fst)
  found : Σ[ m ∶ Point n ] Least Q m
  found = least Q (λ a → lem (Q a))
    (map₁ (λ { (a , a∈ , pa) → (a , a∈) , pa }) h)

递归在第零层取空点名册,两条序律由空性成立。到后继层,上一层的点名册经可定义幂集提升。最先分歧比较利用两条子集前提给出三歧,并直接从上一层的序推出传递性;只有在成员关系需要于该层与其可定义幂集之间转换时,才使用后继层恒等式。

基例把三个字段装配成一个记录,三者正是刚建立的三件小事。点名册 empty 的长度为零:索引类型 Fin zero 为空,故条目与元素字段都用荒谬模式给出,即从无可能实参出发的函数。第零层没有可列的东西,这就是该点名册的全部内容。

stageOrder : (n : ℕ) → StageOrder n
stageOrder 0 = record { tally = empty ; tri = triZero ; trans = transZero }
  where
  empty : Tally (finiteStage zero)
  empty = record

第零层点名册的其余字段来自同一个事实。任何被列条目的成员关系证书都不可能出现,因为根本没有索引;而层的覆盖则由 zero-empty 给出:假设 finiteStage zero 有元素便导出矛盾。因此,empty 在两个方向上都确实枚举了空层。

    { size   = zero
    ; item   = λ ()
    ; inside = λ ()
    ; onto   = λ x x∈ → ⊥₀-rec (zero-empty x x∈) }
  triZero : (x y : S) → ⟨ x ∈ˢ finiteStage zero ⟩ → ⟨ y ∈ˢ finiteStage zero ⟩

两条序字段都是空洞的。零处的三歧收到 x 与 y 的成员关系证书,但这样的证书不存在,zero-empty 从第一个提取矛盾并了结目标。零处的传递收到类型为 before zero x y 的前提,按 before 的计算规则它是假真值,由 ⊥*-rec 消去。空前提给出空结论;除「这个序是空的」之外,没有使用空序的任何性质。

          → Tri ⟨ before zero x y ⟩ (x ≡ y) ⟨ before zero y x ⟩
  triZero x y x∈ y∈ = ⊥₀-rec (zero-empty x x∈)
  transZero : (x y z : S) → ⟨ before zero x y ⟩ → ⟨ before zero y z ⟩
            → ⟨ before zero x z ⟩
  transZero x y z h k = ⊥*-rec h

后继步需要层 n 的三类数学输入:其最小元原理、before n 的三歧与传递性,以及其元素的一份点名册。前两类使最先分歧成为该层诸子集上的严格比较,点名册则通过布尔掩码枚举这些子集;三者共同给出层 suc n 所需的点名册与序律。

stageOrder (suc n) = record { tally = raised ; tri = triSuc ; trans = transSuc }
  where
  module Prev = Ordered n (stageOrder n)
  module Diff = Difference (before n) (finiteStage n) Prev.tri Prev.trans Prev.leastMem
  module Power = PowerStep (# n) (numeral-ord n) Prev.tally

认同 step 是路径 Lset-suc (# n),它断言 n 的后继层就是层 n 的可定义幂集。新点名册 raised 保留幂集点名册的长度与条目,因此枚举的是同样的可定义子集;改变的只是成员关系证书从何处读取,这正是 step 进入之处。

  step : finiteStage (suc n) ≡ 𝒟ₒ (finiteStage n)
  step = Lset-suc (# n)

  raised : Tally (finiteStage (suc n))
  raised = record
    { size   = Tally.size Power.powerTally

inside 字段把每份成员关系证书沿 step 的逆向从可定义幂集传输到后继层,因为证书证明的是在幂集中的成员关系,而点名册声称的是在 Lset (# suc n) 中的成员关系。对称地,onto 取后继层的成员关系证书,先沿 step 向前传输,再调用幂集点名册的覆盖。两个方向的传输都只作用于一句成员关系陈述,别无其他。

    ; item   = Tally.item Power.powerTally
    ; inside = λ i → subst (λ w → ⟨ Tally.item Power.powerTally i ∈ˢ w ⟩) (sym step)
                       (Tally.inside Power.powerTally i)
    ; onto   = λ x x∈ → Tally.onto Power.powerTally x
                          (subst (λ w → ⟨ x ∈ˢ w ⟩) step x∈) }

辅助引理 members 提取 precedes-tri 所要求的包含前提。层 n 的可定义子集的元素都在层 n 中;这就是 𝒟ₒ∋⊆,从可定义幂集中的成员关系反向读出。先沿 step 把 x 的证书传输进幂集,所得是一个函数:对 x 的每个元素 w,给出 w 落在层 n 中的证书。

  members : (x : S) → ⟨ x ∈ˢ finiteStage (suc n) ⟩
          → (w : S) → ⟨ w ∈ˢ x ⟩ → ⟨ w ∈ˢ finiteStage n ⟩
  members x x∈ = 𝒟ₒ∋⊆ (finiteStage n) x (subst (λ v → ⟨ x ∈ˢ v ⟩) step x∈)

  triSuc : (x y : S) → ⟨ x ∈ˢ finiteStage (suc n) ⟩ → ⟨ y ∈ˢ finiteStage (suc n) ⟩
         → Tri ⟨ before (suc n) x y ⟩ (x ≡ y) ⟨ before (suc n) y x ⟩

两条后继字段现在都是一行的应用。triSuc 是 Diff.precedes-tri,两条包含前提由 members 提供,因为 before (suc n) 按定义就是 precedes (before n) (finiteStage n)。transSuc 逐字就是 Diff.precedes-trans,其前提本就具有正确的形状。递归就此闭合:每层的序事实都是上一层的序事实,被最先分歧理论所消费。

  triSuc x y x∈ y∈ = Diff.precedes-tri x y (members x x∈) (members y y∈)

  transSuc : (x y z : S) → ⟨ before (suc n) x y ⟩ → ⟨ before (suc n) y z ⟩
           → ⟨ before (suc n) x z ⟩
  transSuc = Diff.precedes-trans

极限层

Lset ω 的每个元素取得其最小有穷层号;先比较层号、再比较局部层序,便得到极限层良序。

Lset ω 的元素会出现在某个由数码索引的有穷层。在它出现的诸层中,自然数的最小元搜索给出最小者,称为该元素的层号。后文组织不同层号之间的下降时,还会再次使用自然数的良基性。

极限的元素被包装成 Limit:一个集合连同它在 Lset ω 中的成员关系证书。引理 inSome 把这样的证书转换成一句截断的陈述:该集合出现在某个有穷层。从极限层读出证书,仅仅给出某个属于 ω 的 δ,使该集合是 Lset δ 的可定义子集;外层消去的目标是截断类型,而截断类型是命题,故消去正当。

Limit : Type (ℓ-suc ℓ)
Limit = Σ[ x ∶ S ] ⟨ x ∈ˢ Lset ω ⟩

inSome : (x : S) → ⟨ x ∈ˢ Lset ω ⟩ → ∥ Σ[ n ∶ ℕ ] ⟨ x ∈ˢ finiteStage n ⟩ ∥₁
inSome x h = rec₁ squash₁ atStage (Lset-out ω x h)
  where

还需识别 ω 以下的索引 δ。成员关系 δ ∈ ω 是一条截断陈述:存在提升后的自然数 k,使 δ 等于数码 # (lower k)。用 map₁ 在截断内取得该数码见证后,路径把关于 𝒟ₒ (Lset δ) 的可定义子集证书改写到 Lset (# lower k) 上;再由 Lset-suc 把 x 放入 finiteStage (suc (lower k))。这证明了元素出现在有穷层,同时没有混淆索引 ω 与层 Lset ω。

  atStage : Σ[ δ ∶ S ] (⟨ δ ∈ˢ ω ⟩ × ⟨ x ∈ˢ 𝒟ₒ (Lset δ) ⟩)
          → ∥ Σ[ n ∶ ℕ ] ⟨ x ∈ˢ finiteStage n ⟩ ∥₁
  atStage (δ , δ∈ω , x∈) = map₁ named δ∈ω
    where
    named : Σ[ k ∶ Lift ℕ ] (# (lower k) ≡ δ) → Σ[ n ∶ ℕ ] ⟨ x ∈ˢ finiteStage n ⟩

数码命名之后,named 产出实际的出现层。由于 Lset-suc 把 Lset (# (suc k)) 认同为 Lset (# k) 的可定义幂集,「该集合是 Lset (# (lower k)) 的可定义子集」的证书沿 sym (Lset-suc ...) 传输为 finiteStage (suc (lower k)) 中的成员关系。所以索引出现层的数码比出现在 ω 内部的数码多一,这正是索引与其后继层之间常见的差一。

    named (k , q) = suc (lower k)
      , subst (λ w → ⟨ x ∈ˢ w ⟩) (sym (Lset-suc (# (lower k))))
          (subst (λ w → ⟨ x ∈ˢ 𝒟ₒ (Lset w) ⟩) (sym q) x∈)

levelData : (a : Limit)
          → Σ[ n ∶ ℕ ] IsLeast natOrder (λ m → a .fst ∈ˢ finiteStage m) n

levelData 正是截断存在与面向公式的最小元定理相遇之处。谓词 m ↦ a .fst ∈ˢ finiteStage m 由原子成员关系公式呈现,a.fst 与 finiteStage m 分居环境的两个槽位。把自然数序与截断见证 inSome 交给搜索,便返回一个显式数码连同 IsLeast 数据:该数码处的层含有该集合,且更小的数码都没有该性质。因此层号是最小的出现层,而非从截断中任意选出的层。

levelPredicate : (a : Limit) → FOL.Semantics.FormulaPredicate 𝒮ᵥ ℕ (⊥* {ℓ}) ⊥*-rec
  (λ m → a .fst ∈ˢ finiteStage m)
levelPredicate a = FOL.Semantics.presented 2 (var zero ∈̇ var (suc zero))
  (λ m → a .fst ∷ finiteStage m ∷ []) (λ m → refl)

levelData a = leastOfFormula natOrder (levelPredicate a) lem
  (inSome (a .fst) (a .snd))

level : Limit → ℕ
level a = levelData a .fst

level-in : (a : Limit) → ⟨ a .fst ∈ˢ finiteStage (level a) ⟩

两个投影有方便的名字:level a 是底层集合出现的最小数码,level-in a 是该层处的成员关系证书。极限序所需的关于元素「楼层」的一切现在都已是数据,下一节将恰好用这两个材料构造那个序。

level-in a = levelData a .snd .fst

极限上的序先按层号比较:层号较低的元素在前,同层的两个元素则按该层自己的序比较。第二支处理层号相等的情形,其方向使得第二个元素可以在第一个元素的层上读出;正因如此,定义中不出现任何跨层的转换。

非自反与传递是对那一支的分情形,层号等式的情形直接使用相应层上的层序事实。三歧先比较层号,仅当层号相同时才由层序判定。

关系 a ≺ b 是「占先」的两种方式的不相交和。左支说 a 的层号严格更小;右支说两层号相等,并且在层 level a 内,两个底层集合处于该层自己的 before 序中。左支的 Lift 把自然数上的比较从 Type ℓ-zero 抬升到 Type (ℓ-suc ℓ),即右支本已所在的宇宙,于是两支共用一个类型。这个关系按字典序读:层号分出高下,唯有打平时才去问层。

_≺_ : Limit → Limit → Type (ℓ-suc ℓ)
a ≺ b = Lift {ℓ-zero} {ℓ-suc ℓ} (level a < level b)
      ⊎ ((level b ≡ level a) × ⟨ before (level a) (a .fst) (b .fst) ⟩)

limit-irrefl : (a : Limit) → a ≺ a → ⊥₀
limit-irrefl a (inl h)       = ¬m<m (lower h)

非自反性对每一支分别用相应成分的事实处理:严格不等式 level a < level a 被 ¬m<m 拒绝;对 a 自身的 before (level a) 见证被 before-irrefl 拒绝,而后者在每层都成立且无需归纳。传递性则按两个前提各取哪一支来分情形。若两步都在层号上下降,<-trans 复合两个不等式;若只有一步在层号上下降,就用另一前提中的层号等式配合 subst,把那条严格不等式搬到正确的端点,结果仍在左支。

limit-irrefl a (inr (_ , h)) = before-irrefl (level a) (a .fst) h

limit-trans : (a b c : Limit) → a ≺ b → b ≺ c → a ≺ c
limit-trans a b c (inl h)       (inl k)       = inl (lift (<-trans (lower h) (lower k)))
limit-trans a b c (inl h)       (inr (q , _)) =
  inl (lift (subst (λ j → level a < j) (sym q) (lower h)))

当两个前提都取同层支时,a ≺ b 给出 q : level b ≡ level a,b ≺ c 给出 p : level c ≡ level b。复合 p ∙ q : level c ≡ level a 正是 a ≺ c 所需的等式。层序事实 hbc 陈述在 level b;沿 q 传输后落到 level a,便可由 StageOrder.trans 与 hab 复合。

limit-trans a b c (inr (q , _)) (inl k)       =
  inl (lift (subst (λ j → j < level c) q (lower k)))
limit-trans a b c (inr (q , hab)) (inr (p , hbc)) = inr (p ∙ q , joined)
  where
  moved : ⟨ before (level a) (b .fst) (c .fst) ⟩

moved 中的传输沿等式 q 移动 hbc,只改变 before 陈述所处的层,从 level b 换到 level a。此后两个见证便同处一层:hab 说在该层中 a 的集合先于 b 的,moved 说 b 的先于 c 的,于是在层 level a 处用 StageOrder.trans 把二者接成 joined,即 a 在自己层内先于 c 的见证。传递性至此完成;接着陈述三歧性,其判定方式是直接比较两层号。

  moved = subst (λ j → ⟨ before j (b .fst) (c .fst) ⟩) q hbc
  joined : ⟨ before (level a) (a .fst) (c .fst) ⟩
  joined = StageOrder.trans (stageOrder (level a)) (a .fst) (b .fst) (c .fst) hab moved

limit-tri : (a b : Limit) → Tri (a ≺ b) (a ≡ b) (b ≺ a)
limit-tri a b = byLevel (level a ≟ level b)

三歧性先判定 level a ≟ level b。层号不等时立即得到相应的严格比较支。相等支给出 p : level a ≡ level b;沿 sym p 传输 level-in b,便把 b 放入 finiteStage (level a),于是 StageOrder.tri 能在同一层比较两个底层集合。

  where
  byLevel : NatOrder.Trichotomy (level a) (level b) → Tri (a ≺ b) (a ≡ b) (b ≺ a)
  byLevel (NatOrder.lt h) = lt (inl (lift h))
  byLevel (NatOrder.gt h) = gt (inl (lift h))
  byLevel (NatOrder.eq p) = same

局部三歧按极限关系所需的方向重新打包。若局部结果是 a before b,就在 a ≺ b 的同层支中返回 sym p : level b ≡ level a;若结果是 b before a,就在 b ≺ a 的同层支中返回 p,并把 before 证明传输到层 level b。底层集合相等可提升为 Limit 中的相等,因为成员关系证明分量是命题。

    (StageOrder.tri (stageOrder (level a)) (a .fst) (b .fst) (level-in a) b∈)
    where
    b∈ : ⟨ b .fst ∈ˢ finiteStage (level a) ⟩
    b∈ = subst (λ j → ⟨ b .fst ∈ˢ finiteStage j ⟩) (sym p) (level-in b)
    same : Tri ⟨ before (level a) (a .fst) (b .fst) ⟩ (a .fst ≡ b .fst)

重新包装按该层的判定分三种。若 a 的集合先于 b 的,结果是 ≺ 的右支,并以 sym p 供给等式,方向恰是定义所要求的。若两集合相等,Σ≡Prop 把它提升为配对 a 与 b 之间的路径;这是合法的,因为 Limit 的第二个分量是命题,这就是 eq 情形。若 b 的集合先于 a 的,则沿 p 把该 before 事实传输到它应被陈述的层号处,结果是以相反实参给出的右支。这里的每种情形都没有用到已造好的成分之外的任何东西。

               ⟨ before (level a) (b .fst) (a .fst) ⟩
         → Tri (a ≺ b) (a ≡ b) (b ≺ a)
    same (lt h) = lt (inr (sym p , h))
    same (eq q) = eq (Σ≡Prop (λ z → (z ∈ˢ Lset ω) .snd) q)
    same (gt h) = gt (inr (p , subst (λ j → ⟨ before j (b .fst) (a .fst) ⟩) p h))

良基性的证明是两层嵌套的归纳,而把它们分开是有意的。外层是对层号的归纳,采用库中现成的封装,它提供一条覆盖所有更低层的归纳假设。内层沿有穷层已有的可及性作普通的下降,其合法性正来自该层的有穷性。跨层下降的一步使用外层假设,层内的一步使用内层假设;内层函数除自己的可及性实参外不沿任何东西递归,因此二者从不需要同时比较。

固定层号为 k 的目标 b,以及层 k 中与它底层集合相同的点 u。内层论证把 u 关于局部关系 Below k 的可及性转成 b 关于极限关系的可及性。展开 Acc 后,任意前驱记为 c。若 c 的层号更低,就使用外层归纳假设;若层号相同,就把它变成 u 的局部前驱并使用内层可及性。

accInside : (k : ℕ)
          → ((m : ℕ) → m < k → (b : Limit) → level b ≡ m → Acc _≺_ b)
          → (u : Point k) → Acc (Below k) u
          → (b : Limit) → level b ≡ k → b .fst ≡ u .fst → Acc _≺_ b
accInside k ih u (acc ru) b q e = acc step

完成 step 按前提 c ≺ b 所取的支分情形。左支中,c 的层号严格小于 b,因而小于 k;该不等式用 subst 在等式 q 之下搬动,然后在层号 level c 处使用 ih,这正是跨层的情形。右支中,c 与 b 同层号,故二者都在层 k 之内,下降便交给内层可及性:ru 是 u 的可及性的 acc 构造子所提供的函数,把它作用于与 c 对应的点 pc 以及 pc 位于 u 之下的证明。

  where
  step : (c : Limit) → c ≺ b → Acc _≺_ c
  step c (inl h) = ih (level c) (subst (λ j → level c < j) q (lower h)) c refl
  step c (inr (qb , hc)) = accInside k ih pc (ru pc below) c qc refl
    where

右支的簿记需要显式写出。首先 qc 复合两条层号等式,即 sym qb 与 q,证明 level c ≡ k;正是这一点使 c 能被放到层 k 中看。然后 pc 把 c 的底层集合与它在层 k 中的成员关系打包在一起,该成员关系由 level-in c 沿 qc 传输得到。Point k 就是一个集合连同这样的证书,所以这一个构造把论证从极限带回内层序所在的有穷层。

    qc : level c ≡ k
    qc = sym qb ∙ q
    pc : Point k
    pc = c .fst , subst (λ j → ⟨ c .fst ∈ˢ finiteStage j ⟩) qc (level-in c)
    below : Below k pc u

在层号相同的情形,每个极限前驱 b 都与 u 位于同一有穷层 k,并在该层序中低于 u。该分支携带的等式只把两个端点对齐到固定的 k;随后 u 关于 Below k 的可及性给出 b 的可及性。因此,内层递归只沿一个有穷层的序下降。

    below = subst (λ v → ⟨ before k (c .fst) v ⟩) e
              (subst (λ j → ⟨ before j (c .fst) (b .fst) ⟩) qc hc)

accByLevel : (k : ℕ) → (b : Limit) → level b ≡ k → Acc _≺_ b
accByLevel = WFI.induction <-wellfounded outer
  where

外层是对自然数层号的良基归纳,其归纳假设处理层号严格小于 k 的前驱;内层可及性处理仍处于层号 k 的前驱。两种情形合成字典序式的证明,无须假设不同有穷层上的序彼此相容。

  outer : (k : ℕ) → ((m : ℕ) → m < k → (b : Limit) → level b ≡ m → Acc _≺_ b)
        → (b : Limit) → level b ≡ k → Acc _≺_ b
  outer k ih b q = accInside k ih here
    (Ordered.wellFounded k (stageOrder k) here) b q refl
    where

outer 的主体把目标化归到内层引理。它先造出 here,即与 b 对应的层 k 的点,其造法与上面的 pc 完全相同;然后 Ordered.wellFounded k (stageOrder k) here 提供该点在层 k 序中的可及性,accInside 便由此接手,其余两个实参是层号等式 q 以及把 b 的底层集合与 here 的认同起来的自反等式。最后的陈述 limit-wf 说极限的每个元素都可及,做法是在层号 level a 处以平凡等式 refl 实例化层号归纳。

    here : Point k
    here = b .fst , subst (λ j → ⟨ b .fst ∈ˢ finiteStage j ⟩) q (level-in b)

limit-wf : WellFounded _≺_
limit-wf a = accByLevel (level a) a refl

limitOrder : SWO Limit

因此,≺ 是 Limit 上的严格良序:它满足三歧、非自反与传递,而两层归纳证明其良基性。首次出现层号不同的元素按层号排序;只有层号相同的元素才由一个有穷层序比较。

limitOrder = record
  { _<∙_   = _≺_
  ; tri∙   = limit-tri
  ; irr∙   = limit-irrefl
  ; trans∙ = limit-trans

因此,limitOrder 是 Lset ω 诸元素上的严格良序:层号是主键,最小层号相同的元素由该有穷层的序比较。于是,它的最小元运算可从极限层上任意仅仅非空的命题值族中作出选取。

  ; wf∙    = limit-wf }

小结

有穷点名册沿可定义幂集上升,支撑每个数码层处良基的最先分歧序,并最终给出 Lset ω 上的 limitOrder。

Tally 就是本章拥有的全部有穷性:一个命中每个元素的有穷族,既不要求单射,也不要求可判定的相等。PowerStep.powerTally 把它抬到可定义幂集上,办法是枚举点名册上的位向量,并指出已清点层的每个子集都可定义;stageOrder 随后沿诸数码跑完这一步,于是每个有穷层都有一份点名册。

precedes 在两个子集最先分歧之处比较它们。它的非自反性直接由定义推出,传递性由比较两个见证得到,三歧则由排中律连同基底的最小元得到。良基性并不单由这条比较的定义推出;在这里,它经由 Search 从点名册得到。自然数子集上的下降链例子说明了为何有穷层这一假设不可省略。

limitOrder 是 Lset ω 诸元素上的一个严格良序,以层号为主键,层内则用各有穷层自己的序。面向模型的选取把它与 leastOfFormula 组合使用:被搜索的性质由对象语言公式、环境与经过检查的读取定理给出,所得最小元素因而是典范的。