Abstract:这文章旨意用Coq介绍一下Leibniz Equality,并慢慢引出Curry Howard Isomorphism。
前置知识:Haskell
首先,一些设置:
{-# LANGUAGE MonomorphismRestriction, Rank2Types #-}
module Main (
main
) where
import Prelude hiding(EQ)
main :: IO ()
main = return ()
会给出一定的Haskell代码以辅助理解Coq。
Set Implicit Arguments.
Set Universe Polymorphism.
第零行开启Coq中的隐式参数。
第一行开启了Universe Polymorphism,详情见Universes,The Coq Proof Assistant,Girard悖论是什么? - 雾雨魔理沙的回答 - 知乎。不知道这是啥不会影响你的阅读。
当我们认为两个Type相等的时候,我们在说什么?
Monad的创始人(雾,此Monad非彼Monad,不过玄乎程度有过之而不及)Leibniz给出了一个很优雅的解释:当A=B的时候,如果一个命题为真,把里面的任意A替换成B,依然为真。用更formal的话说,
Given any x and y, x = y if and only if, given any predicate P, P(x) if and only if P(y).
用代码表示,就是:
Definition EQ (x y: Type) := forall (p: Type -> Type), ((p x -> p y) * (p y -> p x)). (*这是Comment*) (*为了方便阅读,p x y都显式地标出了类型。*)
换成haskell代码,这是:
newtype EQ x y = EQ {unEQ :: forall p. (p x -> p y, p y -> p x)}
你现在也许在困惑:什么叫做‘用代码表示一个命题’?
我们不严谨的定义是:如果一个类型有值,当且仅当一个命题是定理,这个类型表示这个命题。这时候,这个类型的值就表示了这个命题的证明。
相对应的,如果一个命题不是定理,这个命题对应的定理就没有值-换句话说,同构于bottom type,一个没有任何constructor的type。
我们现在看看,我们这段代码‘代不代表’Leibniz给出的Equality(简称Leibniz Equality)。
假设“P(x)当且仅当P(y)”。
现在有两种情况:P(x),P(y)皆为定理,或皆不是定理。
如果是前者,p x,p y都有值(我们可以构造出p x, p y)。所以,我们可以通过忽略传进来的参数,构造出p x -> p y, p y -> p x。然后,我们可以构造出p x -> p y跟p y -> p x的pair。
如果是后者,p x,p y都同构于bottom type。但是我们可以从bottom type得出一切-如果没明白什么回事,这样想:如果要从有两constructor的sum type返回一个类型A的值,需要进行pattern match,对得出的两个case皆返回A。如果要从有一个constructor的top type返回一个类型A的值,需要进行pattern match,对得出的一个case返回A。如果要从有零个constructor的bottom type返回一个类型A的值,需要进行pattern match,对得出的零个case返回A-而因为只需要对零个case进行操作,什么也不需要做,换句话说我们可以无条件得出A。从另一个角度来想,这是因为我们永远不可能构造出bottom type的值,所以当bottom type作为参数的时候,这个函数永远不可能被调用,自然可以返回随便啥(包括另一个bottom)。
我们现在假设EQ x y有值。
如果如此,当p x有值,换句话说P(x)为真的时候,我们可以通过调用p x -> p y得出p y也有值,换句话说P(y)也为真。 反之亦然。顺着这条路想想,我们发现,如果类型x对应命题X,y对应Y,类型x -> y对应X imply Y:给我一个X的证明,我能给你一个Y的证明。相似的,(x * y)对应X and Y:X的证明在左边,Y的证明在右边。x + y则对应或:x有证明,或者y有证明。Bottom Type跟Top Type则分别对应False, True,而x -> bottom则对应not x。这几个都很明显,自己试试看。
所以我们可以认为这个EQ代表了Leibniz Equality。
我们现在试下证明EQ是reflexive,symmetric,transitive的吧。
首先,reflexive的证明:
然后我们用“Show Proof.”打印出Coq给我们生成的EQRefl。
引入p后,我们只需要证明p x -> p x跟p x -> p x,只需要两个id就能证出来了。
下面打印的是EQRefl。由于我们还没证明完EQRefl,有一个叫?Goal的东西存在:这代表我们剩下的Goal(或者说,subgoal),为右上角的东西。
这个完成了一半的EQRefl有一个参数,p : Type -> Type,就是上面刚刚引入的p。引入假设,相当于lambda abstraction。
这些各种各样的命令,都旨在对Show Proof产生的partial term发生影响,并且使之更具体,所以这样编程就叫Programming By Refinement。
最后剩下的,很简单-就在假设中,我们用intuition让Coq自动帮我们填入。
最后,用Defined完成这个证明。
相对应的Haskell代码是,
eqRefl :: EQ x x
eqRefl = EQ (id, id)
然后下一个。
首先,我们有EQ x y,所以对于出现在任意地方的x,我们可以转成y,也可以y转x。
然后,我们可以生成EQ x x。
所以,我们只需要把EQ x x的第一个x改成y,就可以。
Show Proof.中,出现了一个let X0 := X (fun h => EQ h x),就是specialize后的X。把一个定理变成更特殊的形式,就是定死一个参数。
newtype EQSwap x y = EQSwap {unEQSwap :: EQ y x}
eqSym :: EQ x y -> EQ y x
eqSym (EQ x) = unEQSwap $ fst x $ EQSwap eqRefl
然后,我们证明EQTrans。具体思路挺简单,我们把EQ x y的y换成z。
我们先unfold并intros。
eqTrans :: EQ x y -> EQ y z -> EQ x z
eqTrans xy (EQ (yz, _)) = yz xy
Proof Editing Mode的一个好处是,我们可以通过元编程(或者调用已有的命令),使得Coq自动帮我们证明类型,不需要什么都自己来。
然后,Coq自己标准库里面也有一个equality,“=”。我们可以用eq_refl创造x = x,并,给出H: x = y,可以用rewrite H把出现的x变成y。
至于Leibniz Equality比Coq默认的好在那?这个可以用在Scala中,在Haskell中不需要GADT支持,Typing Dynamic Typing也是用的这。
PS:EQ的两个参数不一定是Type,但是为了跟Haskell版对应就这么做了。
同时,这些代码比对应的HS代码长很多,因为这是在展示CH同构,而不是用最简单的方法做事。
并且,发现我们只用了左边的转换了吗?其实
forall (p: Type -> Type), ((p x -> p y) * (p y -> p x))
forall (p: Type -> Type), (p x -> p y)
x = y
三者等价。可以自己用纸跟笔试试看,或者在Haskell中证明前两个一样。
最后:这个文章很不严谨,只是引起兴趣作用,Coq/CH同构都没介绍多少,有兴趣的话,最好看看下列的文章。
Future Reading:
Curry Howard Isomorphism:
Lectures on The Curry Howard Isomorphism
Coq:
顺带打个广告,有个Coq的QQ群,372347110
Leibniz Equality: