在讨论 Idris 中定理证明的细节之前,我们先介绍一些基本概念:

  • 命题与判断(Propositions and Judgments)

  • 布尔逻辑与构造性逻辑(Boolean and Constructive Logic)

  • Curry-Howard 对应(Curry-Howard Correspondence)

  • 定义等式与命题等式(Definitional and Propositional Equalities)

  • 公理化与构造性方法(Axiomatic and Constructive Approaches)

命题与判断(Propositions and Judgments)

命题是我们证明的对象。在证明之前,我们无法正式判断它们是否为真。如果证明成功,那么结果就是一个'判断'。例如,如果``proposition``是,

1+1=2

当我们证明它时,判断 为:

1+1=2 成立(true)

或者如果 命题 为:

1+1=3

我们无法证明它为真,但它依然是一个有效命题,也许我们可以证明它为假,因此 判断 为:

1+1=3 不成立(false)

这看起来有些迂腐,但必须谨慎:在数学中,并非每个命题都是真或假。例如,有些命题可能尚未被证明,甚至无法被证明。

因此,这里的逻辑不同于布尔代数中的逻辑。在布尔逻辑中,非真即假,非假即真。而我们这里使用的逻辑并不具备这种"排中律(Law of Excluded Middle)",因此不能使用它。

一个假命题被视为矛盾(contradiction),而一旦有矛盾就可以推出任何结论,因此我们要避免这种情况。一些用于证明助理的语言会防止矛盾的出现。

我们使用的逻辑被称为构造性逻辑(constructive logic,或称直觉主义逻辑),因为我们是在构建一个"判断数据库(database of judgments)"。

Curry-Howard 对应(Curry-Howard Correspondence)

那么,如何将这些证明与 Idris 程序关联起来?事实证明,构造性逻辑与类型理论(type theory)之间存在对应关系。它们具有相同的结构,我们可以在两种记法之间自由切换。

其原理是:命题就是类型(a proposition is a type),因此...

Main> 1 + 1 = 2
2 = 2

Main> :t 1 + 1 = 2
(fromInteger 1 + fromInteger 1) === fromInteger 2 : Type

......既是命题,也是类型。下列写法同样会产生一个等式类型:

Main> 1 + 1 = 3
2 = 3

这两者都是有效命题,因此也是有效的等式类型。但我们如何表示一个"成立的判断"呢?也就是说,如何表示 1+1=2 成立而 1+1=3 不成立?一个"成立"的类型是可被实例化(inhabited)的,即它可以被构造。等式类型只有一个构造器 'Refl',因此 1+1=2 的证明为:

onePlusOne : 1+1=2
onePlusOne = Refl

既然我们可以用类型表示命题,命题逻辑的其他方面也可以如下转化为类型:

命题(propositions)

可能的类型示例(example of possible type)

A

x=y

B

y=z

与(and)

A /\ B

Pair(x=y,y=z)

或(or)

A \/ B

Either(x=y,y=z)

蕴含(implies)

A -> B

(x=y) -> (y=z)

对所有(for all)

y=z

存在(exists)

y=z

与(合取,And/Conjunction)

我们可以有一个与合取对应的类型:

AndIntro : a -> b -> A a b

内置有一个名为 'Pair' 的类型。

或(析取,Or/Disjunction)

我们可以有一个与析取对应的类型:

data Or : Type -> Type -> Type where
OrIntroLeft : a -> A a b
OrIntroRight : b -> A a b

内置有一个名为 'Either' 的类型。

定义等式与命题等式(Definitional and Propositional Equalities)

我们已经看到,可以通过构造一个项来"证明"一个类型。对于等式类型,只有一个构造器 Refl。我们还看到,等式两边不必像 '2=2' 那样完全相同,只要两边*定义上相等*即可,例如:

onePlusOne : 1+1=2
onePlusOne = Refl

该等式两边都归约为 2,因此 Refl 匹配,命题得证。

我们不必局限于具体项,也可以使用符号参数,因此如下类型也是可通过类型检查的:

varIdentity : m = m
varIdentity = Refl

如果一个命题/等式类型不是定义等价但依然为真,那么它就是*命题等价(propositionally equal)*。在这种情况下,我们仍然可以证明它,但证明过程中可能需要对项做一些变换,或者采取一些间接步骤来完成证明。

尤其是在处理包含变量项(如函数内部)的等式时,判断哪些等式类型是定义等价的会比较困难。在本例中,plusReducesL 是*定义等价*,而 plusReducesR 不是(尽管它是*命题等价*)。两者唯一的区别在于操作数的顺序。

plusReducesL : (n:Nat) -> plus Z n = n
plusReducesL n = Refl

plusReducesR : (n:Nat) -> plus n Z = n
plusReducesR n = Refl

检查 plusReducesR 时会出现如下错误:

Proofs.idr:21:18--23:1:While processing right hand side of Main.plusReducesR at Proofs.idr:21:1--23:1:
Can't solve constraint between:
        plus n Z
and
        n

那么,为什么 Refl 能证明某些等式类型而不能证明其他的?

第一个原因是 plus 是按第一个参数递归定义的。因此,当第一个参数为 Z 时可以归约,但第二个参数为 Z 时则不能。

如果一个等式类型可以仅用 Refl 证明/构造,则称为*定义等式(definitional equality)*。要定义等价,等式两边必须归约为同一个值。

因此,在 Idris 中输入 1+1 时会立即归约为 2,因为定义等式是内建的。

Main> 1+1
2

在接下来的页面中,我们将讨论如何解决命题等式。