看《The Little Typer》的时候,由于已经看过了 PLFA 第一分册,因此很多东西是 trivial 的,是剥离了 Agda 语法糖以后的再认识,其中也有很多新知识,有必要记下来。
第 14 章 “There’s Safty in Numbers” 讨论在编译期证明程序合法性的问题。索引 (List E) 的 list-ref 函数的返回类型是 (Maybe E),而 vec-ref 则应做得更好——Vec 的类型签名本身就带有长度信息,应当直接检查传入的参数是否合法,不合法则通不过编译。
Pie 的函数都是完全函数,参数类型是 Nat,则必须允许传入全部自然数值。想要限制这个参数的范围,可以使用 dependent type,加一个 (Less n l) 参数,意义为 n < l 的证据,而 Friedman 则选择使用 Fin 类型。
Fin 应当代表 “finite”,它的定义是:
1 | (claim Fin (→ Nat U)) |
于是 (Fin 0) 是 Absurd,(Fin 1) 是 (Maybe Absurd),(Fin 2) 是 (Maybe (Maybe Absurd))……(Fin (add1 n)) 是 (Maybe (Fin n)),根据一点类型代数知识我们可以知道 (Fin n) 就是恰好有 n 个不同值的类型。于是,(Fin n) 可以和集合 0, 1, ... , n - 1 } 里的元素一一对应,用来索引 Vec E n 里的元素。
让我们再来看看 (Fin n) 类型的值的结构。Pie 其实没有内置 Maybe,而是由用户自定义为 (λ (E) (Either E Trivial)),其中 Trivial 是只有一个元素 sole 的类型。为了方便我们只展开到 Maybe 一级。由上文可知 (Fin n) 是 n 层 Maybe 套娃,每一层要么是 (right sole),这时就到头了,要么是 (left ...),开始下一层套娃。所以:
(Fin 1)有一个元素:(right sole);(Fin 2)有两个元素:(right sole),(left (right sole));(Fin 3)有三个元素:(right sole),(left (right sole)),(left (left (right sole)));(Fin 4)有四个元素:(right sole),(left (right sole)),(left (left (right sole))),(left (left (left (right sole)))); ……
容易发现,(Fin n) 的值的结构和 Nat 很像,(right sole) 对应 zero,left 对应 add1,那么我们可以用类似构造 Nat 的方式构造出 (Fin n):
1 | (claim nothing (Π ((E U)) (Maybe E))) |
例如 (Fin 7) 里的 “2”(指和 (the Nat 2) 对应的元素)就可以用 (fadd1 6 (fadd1 5 (fzero 4))) 构造,如果假装 Pie 有隐式参数,写成 (fadd1 (fadd1 (fzero))) 就更像一个 2 了。
那么 vec-ref 也就清晰了:接受一个 fin : (Fin n) 和一个 vec : (Vec E n),然后递归解构 vec 和 fin,直到 fin 解构成 (right sole),返回此时的 vec 中元素。代码如下:
1 | (claim vec-ref (Π ((E U)(l Nat)) (→ (Fin l) (Vec E l) E))) |