This code depends on agda-stdlib:

{-# OPTIONS --without-K #-}
open import Data.Nat
open import Data.Bool

open import Relation.Binary.PropositionalEquality

-- this code doesnt check, cannot match e with refl
why : (e : Bool ≡ Bool) -&> ?
why refl = zero

but-why : (e : 1 ≡ 1) -&> ?
but-why refl = zero

I know K-rule means I cannot match ?a.a≡a with refl, but if a is a concrete value, it can (i.e. 1≡1 can be matched with refl). But why I cannot match Bool≡Bool with refl? Why is type and value are treated differently in a dependently-typed programming language?

(Maybe related: What does it mean if we disable K-rule in Agda?)


感覺和 Agda 關係不大, 和 dependent types 理論本身有關. [公式] 是一種 atomic type (即 [公式] ), 但是 Martin-L?f 好像沒有引入關於 [公式][公式] 之間有什麼關係的規則[1], 倒是最近的 identity type 有這種規則[2], 即:

[公式]

即引入了一個 [公式] 這樣的函數獲取和 identity 有關的一種 atomic type. 其實有我猜的成分... 可以搜一些理論的資料.

[1] Martin-L?f dependent type theory

[2] identity type in nLab

EDIT: 以上答案理解有誤, 等 ice1000 後續的回答.

EDIT: 重新整理一下回答, 以上的回答幾乎全錯就不用看了.

首先 extensional type theory 要求所有的 identity types 都是命題, 換句話說, 類型 [公式][公式] 中所有項構成的 identity types, [公式] , 就像平面上的點, 它們的 (propositional) equality 由所有的點決定真值 (即當前 [公式] 是否能導出 [公式] ). 非 extensional 的 type theory 即稱為 intensional type theory.

忽略定義上的 extensionality, 命題上的 extensionality 要求兩個項的 propositional equality 必須僅由一種方式確認 (所以也能解釋為什麼 intensional 的 homotopy type theory 中 identity types 也可以寫成 [公式] , 它被 path space objects 形式化了, 不僅只有一種方式 equal), 引入公理可將理論從 intensional 轉向 extensional, 如 axiom K, axiom UIP (uniqueness of identity proofs), 兩者不太相同:

  • Axiom K 指某類型 [公式] 所有項與自身的 identity types, [公式] , 和該類型的 reflexivity equality (習慣上稱為 [公式] ), 即 [公式] , 在命題上是相同的 (BTW, 後者的定義引入了新的項, 而 axiom J 歷史上是一種 eliminator, 故 K 以此命名). 換句話說, [公式] . Agda 可以將 [公式] 命題 pattern match 起來.
  • 而 axiom UIP 的出發點不太一樣, 如果任意兩個項 (或命題) 擁有同一個 identity type, [公式] , 則兩者相同.

所以題目的現象就是, [公式] 不能 check 為 refl 是因為函數計算的結果不同 (比如把自然數 3 的 GADT 實例想像成 [公式] 這樣一個 lambda 函數, 自然不同於非 3 的函數), 而 [公式] 同理. 去除 axiom K 後, 類型系統變為 intensional, 對應的 pattern match 規則也被刪除了, 從而新的 propositional equality 方法需要被引入進來, 但數值 desugar 對應的數據實例不受影響 (又不和 identity types 的判別有關, 對吧?).

BTW, Agda 的 propositional equality 對 GADT 自然是有效的, 但是強硬聲明兩個函數就不行了, 如:

open import Data.Bool
open import Relation.Binary.PropositionalEquality using (_≡_; refl)

f : Bool → Bool
f true = true
f false = false

g : Bool → Bool
g true = true
g false = false

_ : f ≡ g
_ = refl

瞬間就爆炸了... 說明 Agda 不純, 也是有函數名這個 effect 的! (實用主義萬歲!)

ice1000 的回答延伸的蠻多的, 我的水平和精力有限, 只能把一些能吸收的總結一下, 如有錯誤之處煩請告知, 真的非常感謝.

參考:

[3] https://ncatlab.org/nlab/show/extensional+type+theory

[4] https://ncatlab.org/nlab/show/identity+type

[5] http://semantic-domain.blogspot.com/2016/03/agda-is-not-purely-functional-language.html


參考論文:Pattern Matching Without K

我的解釋是這樣子的:

留意到Bool≡Bool是對Type的Identity,而1≡1則是對Element的Identity。你去挑出一個element≡element的成員的話……只有refl,因為element≡element是一個proposition,每一個element都是一個contractible(proposition之下的居留元素必須是contractible)——加粗地這一段話算是自己的腦補,只是從hott的角度給出的解釋(意味著可能不普適);而且我還想問一下Type≡Type是不是真的不是mere proposition——

總之,我覺得有另外一個問題需要被解答。我們知道Bool≡Bool之下有兩個成員true≡true和false≡false(因此Type≡Type好像就不是Proposition Type了),那麼true≡true之下的成員是什麼?不論在有無K的意義下,它是否能被叫做refl?

……還是繼續上面的話吧,對於Type≡Type之間的Identity,你如果想要給出證明,在K成立的情況下,這很好辦,取refl就行了。否則的話,你就要自己去挑出其中一個成員,而且這些成員個個都不想等——你挑出一個成員的目的是為了把任何成員match成為這個成員,於是這個成員就是一個refl。

直觀上講,pattern match存在一個類似於「coerce」的過程,你如果要把一個看起來完全不一樣的字元串match到另外一個東西,你需要有一個規則去確保這個過程可以進行。對於Equality的match,讓我們來看看它的「coerce」:

corece-id : (e : Bool≡Bool) -&> coerce e true≡true
coerce-id refl = refl

順便一提,coerce的signature是這樣子的:

coerce : A≡B -&> A -&> B

放在上面就是要把true≡true match成為Bool≡Bool裡面的任意證明,否則match不了false≡false。

coerce-id的意思是任何對bool≡bool的證明都可以推出true≡true(同時這個東西就已經被選作refl,或者說默認之下true≡true絕對可以被match成refl,但是其它的未必)這樣的證明,所以為了讓這個命題成立,coerce-id恰好就需要K——「任何A≡A之下的成員都和一個選定的叫做refl的成員相等」。

所以第一個refl不能被match。恰好對應題目中的情況。

順便一提,上面這個coerce-id,形式化地寫出來是這樣子的:

[公式]

這麼大一個K在兩隻lambda中間飛。

----------------

寫完以後才發現和另外一個答案高度重合,好吧,此答案作廢……


首先對我睡了14個小時感到抱歉:

首先我使用另一份命名更好康、不依賴那難以安裝的標準庫(這樣等我日後抄襲自己也更方便)的定義:

我來分析這麼三件事:

  • 為什麼Coq裡面kksk-why能過(其實因為我不會Coq,這個是我猜的,會留個問題等你們求證)
  • 為什麼Agda裡面kksk-wtf過了不會造成任何inconsistency

首先,我們要知道這個代碼在去掉頭上那個--without-K的擴展後是可以過的,都可以過,都過,隨便過。也就是說,kksk-why的證明需要K規則(有人叫K公理)。

而K規則和 uniqueness of identity proof 有關,也就是說,類型為 [公式] 的變數只有一個——refl。沒有K的時候就是說可以有很多的意思。但無論如何,只要我們有一個 type level 的 identity proof,我們就可以寫出一個transport(有人叫coerce)函數——如果兩個類型被證明為相等,那麼它們的實例就可以互相轉換。在K意義下,就是相等的類型都一定是同一個類型,所以實例平凡可轉換(這就是一個運行時id而已)。在 univalence axiom 意義下,兩個類型同構,那麼使用雙射中其中一個就可以互相轉換,這個轉換是安全的(在K和無K意義下都成立,因此顯然不需要K):

coerce : ? {l} {A B : Set l} → A ≡ B → A → B
coerce refl a = a

在有K意義下,我們可以說coerce 就是運行時id,我們可以把這個性質表達為命題:

稍微分析一下上面的定義,可以發現,kksk-why是直接利用了K公理把identity type給match成了refl,就可以利用『 [公式] 成立時,Bool的實例通過coerce操作得到的一定是自己』,也就是一個泛化的coerce-id。而kksk-wtf則是間接實現了一個kksk-why,它先實現了一個泛化其中一個類型的版本kksk——在這個時候,兩個類型的identity type被match成refl,但由於不知道其中一個類型是啥,所以也就不知道它的實例有啥, 在kksk的上下文里我們是沒法使用它證明coerce-id的(想想看,在ua意義下,你可以把一個true變成一個你不知道的東西a(也就是說,沒有coerce-id),但acoerce變回來則一定是true。這是很合理的。在K意義下,你能確定a的值就一定是true(這就是coerce-id)。此時衝突已經開始展現了)。

而我們實現的kksk-wtf則是特化了kksk,所以能得到和kksk-why相同的類型。

kksk-why的上下文中,有coerce-id這一性質,因為模式匹配就在當前上下文里進行。kksk-wtf中,對 [公式] 的模式匹配是被封裝進另一個泛化版本的函數的,而這個泛化版本的函數沒有更詳細的信息,無法證明coerce-id,然後你在外面的特化的函數根本沒有這個模式匹配(模式匹配被封裝進了一個子上下文),你也就拿不到 『[公式] 被模式匹配成refl』這一悲慘事實了,也就證明不了coerce-id

這解釋角度比較軟體工程但是是正確的。

而為什麼 Coq 就能接受kksk-why的存在呢?

根據 CS 的人說,Coq 直接幫你泛化了這一步,但是在你試圖搞事(證明coerce-id的時候)就會拒♂絕你(等待 Coq 大佬求證)。

我們從反面解釋一下K和ua的衝突性。

假設我們有ua,也有K!我們可以根據Bool -&> Bool的函數not(就Haskell Prelude那個,我就不寫代碼了)構造出一個 [公式] 的證明p。聰明的小朋友已經發現了,根據coerce ,我們可以coerce p true得到false。再配合K公理,我們可以證明true = false


恐怕還是跟實現有關,Coq 里這麼寫就沒問題嘛。


其他大佬們的回答已經很好的解釋了問題了,只缺一份可以動手實驗的代碼了!於是補上(逃

module EssenceOfIdentity

data Id : (A : Type) -&> (x : A) -&> (y : A) -&> Type where
Refl : (A : Type) -&> (x : A) -&> Id A x x

{-
Id
: (A : Type)
-&> (x : A)
-&> (y : A)
-&> Type
-}
-- Id admitted

refl
: (A : Type)
-&> (x : A)
-&> Id A x x
refl = Refl
-- refl admitted

indId
: (A : Type)
-&> (C : (x : A) -&> (y : A) -&> Id A x y -&> Type)
-&> (c : (x : A) -&> C x x (refl A x))
-&> (x : A)
-&> (y : A)
-&> (p : Id A x y)
-&> C x y p
indId _ _ c x x (Refl _ _) = c x
-- indId admitted

axiomK
: (A : Type)
-&> (a : A)
-&> (C : Id A a a -&> Type)
-&> (c : C (refl A a))
-&> (p : Id A a a)
-&> C p
axiomK _ _ _ c (Refl _ _) = c
-- axiomK admitted

coerce
: (A : Type)
-&> (B : Type)
-&> (p : Id Type A B)
-&> (a : A)
-&> B
-- coerce _ _ (Refl _ _) a = a
coerce _A _B p a = (indId Type (x, y, _ =&> x -&> y) (\_ =&> id) _A _B p) a

coerceId
: (A : Type)
-&> (p : Id Type A A)
-&> (a : A)
-&> Id A (coerce A A p a) a
coerceId _A p a = axiomK Type _A (q =&> Id _A (coerce _A _A q a) a) (refl _A a) p

(另外Idris大法好www,光速逃


後續補充:另外為什麼Coq能過確實是因為其高大上,可以從以下代碼看出大致原因:

-- Try to implement axiomK using indId. You will find out it is impossible.
axiomK
: (A : Type)
-&> (a : A)
-&> (C : Id A a a -&> Type)
-&> (c : C (refl A a))
-&> (p : Id A a a)
-&> C p
-- axiomK _ _ _ c (Refl _ _) = c
axiomK _A a _C c p = (indId _A (x, y, _p
=&> (__C : Id _A x y -&> Type)
-&> (q : Id _A x y)
-&> __C q
-&> __C _p) (x =&> (\_, _ =&> ?helpless)) a a p) _C (refl _A a) c

-- But simple case will work just fine.
simpleCase
: (A : Type)
-&> (a : A)
-&> (p : Id A a a)
-&> ()
-- simpleCase _ _ (Refl _ _) = ()
simpleCase _A a p = (indId _A (\_, _, _ =&> ()) (\_ =&> ()) a a p)

其實可以發現實際上Agda的without-K屬於一竿子打死,會影響到一般情況,造成一些反直覺的例子,所以說pattern match和unification這種語法糖在DP里還是不那麼trivial的,因為要將其轉換成具體的induction term,而轉換不一定成功。


再補充:想到一個更好的例子說明為什麼難以用indId實現axiomK,代碼如下:

axiomK
: (A : Type)
-&> (a : A)
-&> (C : Id A a a -&> Type)
-&> (c : C (refl A a))
-&> (p : Id A a a)
-&> C p
axiomK _A a _C c p = (indId _A (x, y, _p
=&> (__C : Id _A x y -&> Type)
-&> __C (indId _A (\_x, _y, _ =&> Id _A _x _y) (\_x =&> refl _A _x) x y _p)
-&> __C _p) (x =&> (\_, _c =&> _c)) a a p) _C ?helpless

可以發現這裡嘗試進行type level的pattern match然後達到構造出(refl _A _x) : (Id _A _x _y)的term的目的,但是仍然行不通。因為_p在這個位置是未知的,所以pattern match這個term無法進行reduce,即使我們知道在K的意義下其唯一的值只可能是refl。


推薦閱讀:
相关文章