4

This code depends on agda-stdlib:

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

open import Relation.Binary.PropositionalEquality

-- this code doesn't 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 $ \forall a.a \equiv a $ with $ refl $, but if $a$ is a concrete value, it can (i.e. $1 \equiv 1$ can be matched with $refl$). But why I cannot match $Bool \equiv Bool$ with $refl$? Why is type and value treated differently in a dependently-typed programming language?

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

ice1000
  • 980
  • 6
  • 35

2 Answers2

7

From the perspective of Homotopy Type Theory (HoTT), i.e. if we have the Univalence Axiom, there are definitely values of Bool ≡ Bool that are distinct from refl. Because Agda without Axiom K is compatible with univalence, it can't then assume that refl is the only value of type Bool ≡ Bool.

ℕ, on the other hand, is an h-set in HoTT (see Section 2.13 of the HoTT book). This is to say that Axiom K restricted to ℕ does hold. This is related to the fact that ℕ is inductively defined (with no parameters or indices) while Set is not.

I don't know the exact logic Agda uses. Presumably, it is something like it knows that nullary constructors of an inductive type are identified with themselves in only one way (which wouldn't be true for higher inductive types), and it knows suc is injective.

Derek Elkins left SE
  • 12,179
  • 1
  • 30
  • 43
4

Essentially, even without K, you can match against refl, but at least one of the endpoints must be an "unconstrained" variable. Both of these type check in Agda-sans-K:

foo : ∀ { A B : Set } -> A ≡ B → A → B
foo refl a = a

fooℕ : ∀ { A : Set } -> A ≡ ℕ → A → A
fooℕ refl a = a + 3

These cases are (probably) handled by a few standard induction principles for equality (AKA dependent elimination principles), such as "path induction" and "based path induction", which do not rely on axiom K, but are a fundamental ingredient in the definition of the equality type.

Instead, the following does not type check, since the two endpoints are constrained to be the same.

bar : ∀ { A : Set } -> A ≡ A → A → A
bar refl a = a

I'm unsure about why this does not work as expected. By comparison, in Coq this works fine:

Definition bar: forall A : Type, A = A -> A -> A :=
   fun A p x =>
   match p with
   | eq_refl => x
   end .

As the translation of the OP's code:

Definition foo: (bool = bool) -> nat :=
   fun p =>
   match p with
   | eq_refl => 0
   end .

In these two last cases, we do not even need dependent elimination, the regular non-dependent one suffices.

I guess that Agda, when no endpoint is "free", internally translates the match into some form which relies on axiom K, even if in some cases (like the above) there is no real need to do so.

One can indeed define the original why pattern match, by generalizing it so that at least one endpoint is "free".

why-generalized : {A : Set} -> (e : Bool ≡ A) -> ℕ
why-generalized {.Bool} refl = zero

why : (Bool ≡ Bool) -> ℕ
why = why-generalized

(Well, in this case we could also use why x = zero, but the point of the above code is to pattern match against refl)

chi
  • 14,704
  • 1
  • 31
  • 40