Built with Alectryon, running Coq+SerAPI v8.19.0+0.19.3. Bubbles () indicate interactive fragments: hover for details, tap to reveal contents. Use Ctrl+↑Ctrl+↓ to navigate, Ctrl+🖱️ to focus. On Mac, use ⌘ instead of Ctrl.
(** * Varieties of function extensionality *)
[Loading ML file number_string_notation_plugin.cmxs (using legacy method) ... done]
Require Import Metatheory.Core.LocalOpen Scope path_scope.(** In the Overture, we defined function extensionality to be the assertion that the map [apD10] is an equivalence. We now prove that this follows from a couple of weaker-looking forms of function extensionality. We do require eta conversion, which Coq 8.4+ has judgmentally. This proof is originally due to Voevodsky; it has since been simplified by Peter Lumsdaine and Michael Shulman. *)(** Naive funext is the simple assertion that pointwise equal functions are equal. The domain and codomain could live in different universes; the third universe argument is essentially the max of [i] and [j] (and similarly for all subsequent axioms). *)DefinitionNaiveFunext :=
forall (A : Type@{i}) (P : A -> Type@{j}) (fg : forallx, P x),
(forallx, f x = g x) -> (f = g).(** Naive non-dependent funext is the same, but only for non-dependent functions. *)DefinitionNaiveNondepFunext :=
forall (AB : Type) (fg : A -> B),
(forallx, f x = g x) -> (f = g).(** Weak funext says that a product of contractible types is contractible. *)DefinitionWeakFunext :=
forall (A : Type) (P : A -> Type),
(forallx, Contr (P x)) -> Contr (forallx, P x).(** The obvious implications are [Funext -> NaiveFunext -> WeakFunext] and [NaiveFunext -> NaiveNondepFunext]. None of these do anything fiddly with the universes either. *)
Funext_type -> NaiveFunext
Funext_type -> NaiveFunext
fe: Funext_type A: Type P: A -> Type f, g: forallx : A, P x h: forallx : A, f x = g x
f = g
fe: forall (A : Type) (P : A -> Type)
(fg : forallx : A, P x), IsEquiv apD10 A: Type P: A -> Type f, g: forallx : A, P x h: forallx : A, f x = g x
f = g
exact ((@apD10 A P f g)^-1 h).Defined.
NaiveFunext -> WeakFunext
NaiveFunext -> WeakFunext
nf: NaiveFunext A: Type P: A -> Type Pc: forallx : A, Contr (P x)
Contr (forallx : A, P x)
nf: NaiveFunext A: Type P: A -> Type Pc: forallx : A, Contr (P x)
forally : forallx : A, P x,
(funx : A => center (P x)) = y
nf: NaiveFunext A: Type P: A -> Type Pc: forallx : A, Contr (P x) f: forallx : A, P x x: A
center (P x) = f x
apply contr.Defined.DefinitionNaiveFunext_implies_NaiveNondepFunext@{i j max}
: NaiveFunext@{i j max} -> NaiveNondepFunext@{i j max}
:= funnfABfg => nf A (fun_ => B) f g.(** The non-obvious directions are that [WeakFunext] implies Funext and that [NaiveNondepFunext] implies [WeakFunext] (and hence all four are logically equivalent). *)(** ** [WeakFunext] implies [Funext] *)(** To show that [WeakFunext] implies Funext, the point is that under weak funext, the space of "pointwise homotopies" has the same universal property as the space of paths. *)SectionHomotopies.Context (wf : WeakFunext).Context {A:Type} {B : A -> Type}.Context (f : forallx, B x).(* Recall that [f == g] is the type of pointwise paths (or "homotopies") from [f] to [g]. *)Letidhtpy : f == f := funx => idpath (f x).(** Weak funext implies that the "based homotopy space" of the Pi-type is contractible, just like the based path space. *)(** Use priority 1, so we don't override [Contr Unit]. *)
wf: WeakFunext A: Type B: A -> Type f: forallx : A, B x idhtpy:= funx : A => 1: f == f
Contr {g : forallx : A, B x & f == g}
wf: WeakFunext A: Type B: A -> Type f: forallx : A, B x idhtpy:= funx : A => 1: f == f
Contr {g : forallx : A, B x & f == g}
wf: forall (A : Type) (P : A -> Type),
(forallx : A, Contr (P x)) ->
Contr (forallx : A, P x) A: Type B: A -> Type f: forallx : A, B x idhtpy:= funx : A => 1: f == f
Contr {g : forallx : A, B x & f == g}
wf: forall (A : Type) (P : A -> Type),
(forallx : A, Contr (P x)) ->
Contr (forallx : A, P x) A: Type B: A -> Type f: forallx : A, B x idhtpy:= funx : A => 1: f == f
forally : {x : _ & f == x}, (f; idhtpy) = y
wf: forall (A : Type) (P : A -> Type),
(forallx : A, Contr (P x)) ->
Contr (forallx : A, P x) A: Type B: A -> Type f: forallx : A, B x idhtpy:= funx : A => 1: f == f g: forallx : A, B x h: f == g
(f; idhtpy) = (g; h)
(* The trick is to show that the type [{g : forall x, B x & f == g }] is a retract of [forall x, {y : B x & f x = y}], which is contractible due to J and weak funext. Here are the retraction and its section. *)
wf: forall (A : Type) (P : A -> Type),
(forallx : A, Contr (P x)) ->
Contr (forallx : A, P x) A: Type B: A -> Type f: forallx : A, B x idhtpy:= funx : A => 1: f == f g: forallx : A, B x h: f == g r:= funk : forallx : A, {x0 : _ & f x = x0} =>
(funx : A => (k x).1; funx : A => (k x).2): (forallx : A, {x0 : _ & f x = x0}) ->
{g : forallx : A, B x & f == g}
(f; idhtpy) = (g; h)
wf: forall (A : Type) (P : A -> Type),
(forallx : A, Contr (P x)) ->
Contr (forallx : A, P x) A: Type B: A -> Type f: forallx : A, B x idhtpy:= funx : A => 1: f == f g: forallx : A, B x h: f == g r:= funk : forallx : A, {x0 : _ & f x = x0} =>
(funx : A => (k x).1; funx : A => (k x).2): (forallx : A, {x0 : _ & f x = x0}) ->
{g : forallx : A, B x & f == g} s:= fun (g : forallx : A, B x) (h : f == g) (x : A) =>
(g x; h x): forallg : forallx : A, B x,
f == g -> forallx : A, {x0 : _ & f x = x0}
(f; idhtpy) = (g; h)
(* Because of judgemental eta-conversion, the retraction is actually definitional, so we can just replace the goal. *)
wf: forall (A : Type) (P : A -> Type),
(forallx : A, Contr (P x)) ->
Contr (forallx : A, P x) A: Type B: A -> Type f: forallx : A, B x idhtpy:= funx : A => 1: f == f g: forallx : A, B x h: f == g r:= funk : forallx : A, {x0 : _ & f x = x0} =>
(funx : A => (k x).1; funx : A => (k x).2): (forallx : A, {x0 : _ & f x = x0}) ->
{g : forallx : A, B x & f == g} s:= fun (g : forallx : A, B x) (h : f == g) (x : A) =>
(g x; h x): forallg : forallx : A, B x,
f == g -> forallx : A, {x0 : _ & f x = x0}
r (funx : A => (f x; 1)) = r (s g h)
apply ap; srapply path_contr.Defined.(** This enables us to prove that pointwise homotopies have the same elimination rule as the identity type. *)Context (Q : forallg (h : f == g), Type).Context (d : Q f idhtpy).Definitionhtpy_indgh : Q g h
:= @transport _ (fungh => Q gh.1 gh.2) (f;idhtpy) (g;h)
(@path_contr _ _ _ _) d.(** The computation rule, of course, is only propositional. *)Definitionhtpy_ind_beta : htpy_ind f idhtpy = d
:= transport (funp : (f;idhtpy) = (f;idhtpy) =>
transport (fungh => Q gh.1 gh.2) p d = d)
(@path2_contr _ _ _ _
(path_contr (f;idhtpy) (f;idhtpy)) (idpath _))^
(idpath _).EndHomotopies.(** Now the proof is fairly easy; we can just use the same induction principle on both sides. This proof also preserves all the universes. *)
WeakFunext -> Funext_type
WeakFunext -> Funext_type
wf: WeakFunext A: Type B: A -> Type f, g: forallx : A, B x
IsEquiv apD10
wf: WeakFunext A: Type B: A -> Type f, g: forallx : A, B x
(funx : f == g =>
apD10
(htpy_ind wf f
(fun (g' : forallx0 : A, B x0) (_ : f == g') =>
f = g') 1 g x)) == idmap
wf: WeakFunext A: Type B: A -> Type f, g: forallx : A, B x
(funx : f = g =>
htpy_ind wf f
(fun (g' : forallx0 : A, B x0) (_ : f == g') =>
f = g') 1 g (apD10 x)) == idmap
wf: WeakFunext A: Type B: A -> Type f, g: forallx : A, B x
(funx : f == g =>
apD10
(htpy_ind wf f
(fun (g' : forallx0 : A, B x0) (_ : f == g') =>
f = g') 1 g x)) == idmap
wf: WeakFunext A: Type B: A -> Type f: forallx : A, B x
apD10
(htpy_ind wf f
(fun (g' : forallx : A, B x) (_ : f == g') =>
f = g') 1 f (funx : A => 1)) = (funx : A => 1)
refine (ap _ (htpy_ind_beta wf _ _ _)).
wf: WeakFunext A: Type B: A -> Type f, g: forallx : A, B x
(funx : f = g =>
htpy_ind wf f
(fun (g' : forallx0 : A, B x0) (_ : f == g') =>
f = g') 1 g (apD10 x)) == idmap
wf: WeakFunext A: Type B: A -> Type f: forallx : A, B x
htpy_ind wf f
(fun (g' : forallx : A, B x) (_ : f == g') =>
f = g') 1 f (apD10 1) = 1
refine (htpy_ind_beta wf _ _ _).Defined.DefinitionNaiveFunext_implies_Funext : NaiveFunext -> Funext_type
:= WeakFunext_implies_Funext o NaiveFunext_implies_WeakFunext.(** ** Naive non-dependent funext implies weak funext *)(** First we show that naive non-dependent funext suffices to show that postcomposition with an equivalence is an equivalence. *)Definitionequiv_postcompose_from_NaiveNondepFunext
(nf : NaiveNondepFunext) {ABC : Type} (f : B <~> C)
: (A -> B) <~> (A -> C)
:= Build_Equiv
_ _ (fun (g:A->B) => f o g)
(isequiv_adjointify
(fun (g:A->B) => f o g)
(funh => f^-1 o h)
(funh => nf _ _ _ _ (funx => eisretr f (h x)))
(fung => nf _ _ _ _ (funy => eissect f (g y)))).(** Now, if each [P x] is contractible, the projection [pr1 : {x:X & P x} -> X] is an equivalence (this requires no funext). Thus, postcomposition with it is also an equivalence, and hence the fiber of postcomposition over [idmap X] is contractible. But this fiber is "the type of sections of [pr1]" and hence equivalent to [forall x:X, P x]. The latter equivalence requires full funext to prove, but without any funext we can show that [forall x:X, P x] is a *retract* of the type of sections, hence also contractible. *)
NaiveNondepFunext -> WeakFunext
NaiveNondepFunext -> WeakFunext
nf: NaiveNondepFunext X: Type P: X -> Type H: forallx : X, Contr (P x)
Contr (forallx : X, P x)
nf: NaiveNondepFunext X: Type P: X -> Type H: forallx : X, Contr (P x) T:= hfiber
(equiv_postcompose_from_NaiveNondepFunext nf
(equiv_pr1 P)) idmap: Type
Contr (forallx : X, P x)
exact (@contr_retract T _ _
(funfpx => transport P (ap10 fp.2 x) (fp.1 x).2)
(funf => ((funx => (x ; f x)) ; 1)) (funf => 1)).Defined.(** Therefore, naive non-dependent funext also implies full funext. Interestingly, this requires the universe of the assumption codomain to be not just that of the conclusion codomain, but the max of that universe with the domain universe (which is unchanged). *)DefinitionNaiveNondepFunext_implies_Funext@{i j max}
: NaiveNondepFunext@{i max max} -> Funext_type@{i j max}
:= WeakFunext_implies_Funext o NaiveNondepFunext_implies_WeakFunext.(** ** Functional extensionality is downward closed *)(** If universe [U_i] is functionally extensional, then so are universes [U_i'] for [i' ≤ i]. *)
H: Funext_type
Funext_type
H: Funext_type
Funext_type
H: forall (A : Type) (P : A -> Type)
(fg : forallx : A, P x), IsEquiv apD10
forall (A : Type) (P : A -> Type)
(fg : forallx : A, P x), IsEquiv apD10
(* Here we make use of cumulativity. *)exact (funAP => H A P).Defined.