Zulip Chat Archive

Stream: new members

Topic: get the witness of `βˆƒ!`?


Yusuke Inoue (Jul 17 2025 at 15:54):

In first order logic, when we have a βˆƒ! prop, we can define a new constant representing the only witness of prop.
Logically, I think it could be done in Lean, but actually not.
Here are two ways I know and why I don't think they're good:

  • using a type in Type instead : that requires much stronger premise
  • using Classical.choose : in FOL AC isn't needed to do that
    The witness of a βˆƒ! prop is actually 'subsingle', so maybe allow getting the witness from that a prop wouldn't lead to inconsistency?

Aaron Liu (Jul 17 2025 at 15:55):

If you allow eliminating out the witness such that elim (mk x proof) = x definitionally then it makes definitional equality undecidable

Aaron Liu (Jul 17 2025 at 15:56):

well, even more undecidable (it was undecidable already because of a similar problem with Acc.rec)

Aaron Liu (Jul 17 2025 at 16:01):

this is the same problem as #Type theory > Can `Squash.mk x = Squash.mk y` be made true by definition? @ πŸ’¬ but with ExistsUnique instead of Squash

Kyle Miller (Jul 17 2025 at 16:01):

My rough understanding here is that to be able to get the witness, you need to have some sort of computational interpretation of proofs for it all to work out (since you'll need to be able to compute with that witness in reductions; I think this touches on what Aaron is saying about undecidability).

There is a computational interpretation of proofs, but Lean tends to avoid doing any proof reductions if it can help it. The compiler will erase proofs for example, so you won't be able to use this for programs, without a lot of extra work to figure out how to modify the compiler.

But, the definitional equality problem that Aaron mentions is a problem. Given two witnesses x and y, proof irrelevance means mk x proof1 = mk y proof2 is a definitional equality, so elim (mk x proof1) = elim (mk y proof2) is a definitional equality, and so x = y is a definitional equality. That's not good, because x and y might not directly be definitionally equal (!). I'm not sure this is a very big problem, given that the definitional equality algorithm already isn't transitive, but it certainly won't make the situation better.

Yusuke Inoue (Jul 17 2025 at 16:05):

Could we simply drop the computational interpretation, making the witness opaque?

Yusuke Inoue (Jul 17 2025 at 16:06):

just like the way it works in FOL, imo

Kyle Miller (Jul 17 2025 at 16:06):

May as well use Classical.choose then I think?

Yusuke Inoue (Jul 17 2025 at 16:09):

eh, I think Classical.choose looks too strong for this purpose.Anyway, in FOL, AC is not needed to do that.

Kyle Miller (Jul 17 2025 at 16:09):

I believe in type theory "getting the witness from a proof" usually means something like "reduce the proof to weak-head normal form ExistsUnique.mk x proof and then use x". Anything else amounts to adding an axiom, where you don't "really" get a witness, just a purported one that's supposed to exist.

Kyle Miller (Jul 17 2025 at 16:11):

My understanding is that FOL doesn't have the same sense of constructions as in the Calculus of Constructions. It's a much more stringent requirement to be able to "get" the witness in CoC than FOL, since "getting" the witness means you need to be able to compute with the witness in some way.

Kyle Miller (Jul 17 2025 at 16:12):

In CoC, you can "get" the witness of even an existential in a more limited sense, using Exists.rec, and that's probably similar to what it means to get a witness in FOL. (I'm no logician though!)

Yusuke Inoue (Jul 17 2025 at 16:17):

Maybe introducing a way to get a witness can better simulate the feeling of doing FOL when actually using Lean, that's what I think when asking this question

Aaron Liu (Jul 17 2025 at 16:17):

How do you "get" the witness in FOL, when you're stating the theorem? That is, if I have a proof that βˆƒ! x, P x how do I state that "Q x where x is the unique x such that P x" in FOL? Clearly you need some sort of quantification to contain the x, and then once you start proving that Q x you don't need any choice in the proof, you just unwrap the exists.

Aaron Liu (Jul 17 2025 at 16:18):

My point is that you can do this in Lean too, it just looks kind of ugly

Yusuke Inoue (Jul 17 2025 at 16:18):

Yes, but without a way to introduce a constant representing the witness, the sentences will easily be quite long.

Aaron Liu (Jul 17 2025 at 16:18):

this happens in FOL too

Yusuke Inoue (Jul 17 2025 at 16:19):

Aaron Liu said:

My point is that you can do this in Lean too, it just looks kind of ugly

Yes that's what I think.

suhr (Jul 17 2025 at 16:19):

Yusuke Inoue said:

Anyway, in FOL, AC is not needed to do that.

In FOL you just never leave the Prop.

Kyle Miller (Jul 17 2025 at 16:19):

I think it wouldn't be too hard of a metaprogramming exercise to make an elaborator that eliminates uses of Classical.choice in a Prop context.

Aaron Liu (Jul 17 2025 at 16:20):

the reason you don't need AC to do this in FOL is because in FOL you have these lengthy sentences that every time they need to reference the unique x that satisfies P they put a quantifier instead and you can do that in Lean too

Kyle Miller (Jul 17 2025 at 16:20):

@Yusuke Inoue Are you familiar with using obtain to get a witness from an existential? That's usually how people do it, rather than using Classical.choose, unless you mean you want to use it inside of other definitions.

Kyle Miller (Jul 17 2025 at 16:21):

I think it would be very useful to have a #mwe to ground this discussion at this point.

Yusuke Inoue (Jul 17 2025 at 16:24):

Aaron Liu said:

this happens in FOL too

I read somewhere that when we have a existsunique prop, introducing its witness as a constant is a conservative extension. Maybe in proof assistants based on FOL they allow users to do that (just guessing, so far I've never used them).

Yusuke Inoue (Jul 17 2025 at 16:25):

Kyle Miller said:

Yusuke Inoue Are you familiar with using obtain to get a witness from an existential? That's usually how people do it, rather than using Classical.choose, unless you mean you want to use it inside of other definitions.

Yes I know obtain

Yusuke Inoue (Jul 17 2025 at 16:27):

Kyle Miller said:

I think it would be very useful to have a #mwe to ground this discussion at this point.

Good idea. I'll give one

Terence Tao (Jul 17 2025 at 19:00):

Some relevant recent discussion: #general > crazy idea: "Type" β†’ "Space", "term" β†’ "point" @ πŸ’¬ . As observed there, the ability to extract witnesses from an ExistUnique is equivalent to having a choice function for non-empty singletons.

I also wrote some basic API for extracting witnesses (using choice) at https://github.com/teorth/analysis/blob/main/analysis/Analysis/Tools/ExistsUnique.lean (this doesn't directly address your question, but helped with the practical concern of just extracting the constant for use in Lean proofs).

Yusuke Inoue (Jul 18 2025 at 04:32):

Yusuke Inoue said:

Kyle Miller said:

I think it would be very useful to have a #mwe to ground this discussion at this point.

Good idea. I'll give one

It's from Tao's analysis I.

Exercise 3.5.12. This exercise will establish a rigorous version of Proposition 2.1.16. Let f:N×N→N f : \mathbb{N} \times \mathbb{N} \to \mathbb{N} be a function, and let c c be a natural number. Show that there exists a function a:N→N a : \mathbb{N} \to \mathbb{N} such that
a(0)=c a(0) = c
a(n++)=f(n,a(n))for alln∈N, a(n\text{++}) = f(n, a(n)) \quad \text{for all} \quad n \in \mathbb{N},
and furthermore that this function is unique.

Hint: First show inductively that for every natural number N N , there exists a unique function aN:{n∈N:n≀N}β†’N a_N : \{ n \in \mathbb{N} : n \leq N \} \to \mathbb{N} such that:
aN(0)=c a_N(0) = c
aN(n++)=f(n,aN(n))forΒ alln<N a_N(n\text{++}) = f(n, a_N(n)) \quad \text{for all} \quad n < N

For an additional challenge: Prove this using only Peano axioms (no ordering properties or Proposition 2.1.16).

Challenge Hint: First show inductively that for every N N , there exists a unique pair of subsets AN,BNβŠ†N A_N, B_N \subseteq \mathbb{N} satisfying:
(a) AN∩BN=βˆ… A_N \cap B_N = \emptyset
(b) ANβˆͺBN=N A_N \cup B_N = \mathbb{N}
(c) 0∈AN 0 \in A_N
(d) N++∈BN N\text{++} \in B_N
(e) If n∈BN n \in B_N , then n++∈BN n\text{++} \in B_N
(f) If n∈AN n \in A_N and nβ‰ N n \neq N , then n++∈AN n\text{++} \in A_N

Then use AN A_N as {n∈N:n≀N} \{ n \in \mathbb{N} : n \leq N \} in the previous argument.

import Mathlib

class PeanoAxioms (N : Type) extends Zero N where
  succ : N -> N
  zero_no_succ : βˆ€n : N, Β¬ succ n = 0
  succ_cancel : βˆ€a b : N, succ a = succ b β†’ a = b
  induction : βˆ€f : N β†’ Prop, (f 0 ∧ βˆ€n : N, f n β†’ f (succ n)) β†’ βˆ€n : N, f n

postfix:100 "++" => PeanoAxioms.succ

namespace PeanoAxioms

variable {N : Type}[PeanoAxioms N]

lemma unique_set_up_to_n : βˆ€n, βˆƒ!p : Set N Γ— Set N ,let (a,b) := p;
  a ∩ b = βˆ… ∧ a βˆͺ b = Set.univ ∧ 0 ∈ a ∧ n++ ∈ b ∧
  (βˆ€m, m ∈ b β†’ m++ ∈ b) ∧ (βˆ€m, (m ∈ a ∧ m β‰  n) β†’ m++ ∈ a) := by sorry

lemma unique_fun : βˆƒ!f : N β†’ Set N Γ— Set N,βˆ€n,let (a,b) := f n;
  a ∩ b = βˆ… ∧ a βˆͺ b = Set.univ ∧ 0 ∈ a ∧ n++ ∈ b ∧
  (βˆ€m, m ∈ b β†’ m++ ∈ b) ∧ (βˆ€m, (m ∈ a ∧ m β‰  n) β†’ m++ ∈ a) := by sorry;

def A (n : N) : Set N := (unique_fun |> ExistsUnique.exists |> Classical.choose) n |> Prod.fst

lemma unique_fn_for_n (f : N β†’ N β†’ N) (c : N) : βˆ€n,βˆƒ!a : {m // A n m} β†’ N,
  (βˆƒh : 0 ∈ A n, a ⟨0,h⟩ = c) ∧ βˆ€m : {m : N // A n m }, ↑m β‰  n β†’ βˆƒh : (↑m)++ ∈ A n, a ⟨(↑m)++,h⟩ = f (↑m) (a m) := by sorry

Without defining A here, the unique_fn_for_n statement will be quite long.
Defining A using existing axioms in Mathlib requires Classical.choose, which seems too strong.

An additional unique_choiceΒ axiom looks appealing to me :upside_down:

Kyle Miller (Jul 18 2025 at 13:45):

Maybe a unique_choice axiom would be a weaker assumption (at the cost of adding a new axiom!) but practically it's usually best to avoid any kind of choice at all if it's possible to construct the objects under consideration.

Something that's missing from the Peano axioms is a recursion principle, which would let you construct these sets without any need for choice. Here it is mostly worked out for creating the sets a and b, minus the part that shows the sets are unique:

import Mathlib

class PeanoAxioms (N : Type) extends Zero N where
  succ : N -> N
  zero_no_succ : βˆ€n : N, Β¬ succ n = 0
  succ_cancel : βˆ€a b : N, succ a = succ b β†’ a = b
  induction : βˆ€f : N β†’ Prop, (f 0 ∧ βˆ€n : N, f n β†’ f (succ n)) β†’ βˆ€n : N, f n
  recurse : βˆ€ (f : N β†’ Type), (f 0) β†’ ((n : N) β†’ f n β†’ f (succ n)) β†’ (n : N) β†’ f n
  recurse_zero : βˆ€ (f : N β†’ Type) (z : f 0) (s : (n : N) β†’ f n β†’ f (succ n)),
    recurse f z s 0 = z
  recurse_succ : βˆ€ (f : N β†’ Type) (z : f 0) (s : (n : N) β†’ f n β†’ f (succ n)),
    βˆ€ (n : N), recurse f z s (succ n) = s n (recurse f z s n)

postfix:100 "++" => PeanoAxioms.succ

namespace PeanoAxioms

variable {N : Type} [PeanoAxioms N]

def A : N β†’ Set N :=
  PeanoAxioms.recurse
    (fun _ => Set N)
    {0}
    (fun m last => insert (m++) last)

def B (n : N) : Set N := Set.univ \ A n

lemma cases (f : N β†’ Prop) (zero : f 0) (succ : βˆ€ n : N, f (succ n)) : βˆ€n : N, f n :=
  PeanoAxioms.induction f ⟨zero, fun n _ => succ n⟩

lemma succ_ne_self : βˆ€ (n : N), Β¬ n++ = n := by
  apply PeanoAxioms.induction
  constructor
  Β· exact PeanoAxioms.zero_no_succ 0
  Β· intro n hn h
    apply hn
    apply PeanoAxioms.succ_cancel
    exact h

lemma inter_eq_empty (n : N) : A n ∩ B n = βˆ… := by simp [A, B]
lemma union_eq_univ (n : N) : A n βˆͺ B n = Set.univ := by simp [A, B]

lemma zero_mem_a : βˆ€ n : N, 0 ∈ A n := by
  apply PeanoAxioms.induction
  constructor
  Β· simp [A, PeanoAxioms.recurse_zero]
  Β· intro n hn
    rw [A] at hn
    simp [A, PeanoAxioms.recurse_succ, hn]

lemma mem_of_succ_mem_a : βˆ€ (n m : N), m++ ∈ A n β†’ m ∈ A n := by
  apply PeanoAxioms.induction
  constructor
  Β· simp [A, PeanoAxioms.recurse_zero]
    intro m h
    exact absurd h (PeanoAxioms.zero_no_succ m)
  Β· intro n ih m
    simp [A, PeanoAxioms.recurse_succ]
    rintro (h | h)
    Β· replace h := PeanoAxioms.succ_cancel _ _ h
      subst h
      right
      revert m
      apply PeanoAxioms.cases
      Β· intro
        simp [PeanoAxioms.recurse_zero]
      Β· intro m _
        simp [PeanoAxioms.recurse_succ]
    Β· right
      exact ih m h

lemma self_mem_a : βˆ€ (n : N), n ∈ A n := by
  apply PeanoAxioms.cases
  Β· simp [A, PeanoAxioms.recurse_zero]
  Β· intro n
    simp [A, PeanoAxioms.recurse_succ]

lemma succ_self_not_mem_a : βˆ€ (n : N), Β¬ n++ ∈ A n := by
  apply PeanoAxioms.induction
  constructor
  Β· simp [A, PeanoAxioms.recurse_zero, zero_no_succ]
  Β· intro n
    apply mt
    intro h
    rw [A, PeanoAxioms.recurse_succ, ← A] at h
    simp [succ_ne_self] at h
    revert h
    apply mem_of_succ_mem_a

lemma succ_mem_a : βˆ€ (n m : N), m ∈ A n ∧ m β‰  n β†’ m++ ∈ A n := by
  apply PeanoAxioms.induction
  constructor
  Β· simp [A, PeanoAxioms.recurse_zero]
  Β· intro n ih
    rintro m ⟨h1, h2⟩
    revert h1
    rw [A, PeanoAxioms.recurse_succ, ← A]
    simp [h2]
    rw [or_iff_not_imp_left]
    intro h3 h4
    apply ih
    refine ⟨h3, ?_⟩
    contrapose! h4
    rw [h4]

lemma succ_mem_b : βˆ€ (n : N), n++ ∈ B n := by
  simp [B]
  apply succ_self_not_mem_a

lemma succ_mem_b_of_mem_b (n m : N) : m ∈ B n β†’ m++ ∈ B n := by
  simp [B]
  apply mt
  apply mem_of_succ_mem_a

lemma unique_set_up_to_n : βˆ€n, βˆƒ!p : Set N Γ— Set N ,let (a,b) := p;
    a ∩ b = βˆ… ∧ a βˆͺ b = Set.univ ∧ 0 ∈ a ∧ n++ ∈ b ∧
    (βˆ€m, m ∈ b β†’ m++ ∈ b) ∧ (βˆ€m, (m ∈ a ∧ m β‰  n) β†’ m++ ∈ a) := by
  intro n
  use (A n, B n)
  dsimp only
  use! inter_eq_empty n, union_eq_univ n, zero_mem_a n, succ_mem_b n, succ_mem_b_of_mem_b n,
    succ_mem_a n
  rintro ⟨a, b⟩
  simp
  sorry -- prove uniqueness

Kyle Miller (Jul 18 2025 at 13:52):

Kyle Miller said:

I think it wouldn't be too hard of a metaprogramming exercise to make an elaborator that eliminates uses of Classical.choice in a Prop context.

More details about this idea:

I'm imagining that you would be able to define A using Classical.choice, and then your proof would look like

lemma unique_set_up_to_n : βˆ€n, βˆƒ!p : Set N Γ— Set N ,let (a,b) := p;
    a ∩ b = βˆ… ∧ a βˆͺ b = Set.univ ∧ 0 ∈ a ∧ n++ ∈ b ∧
    (βˆ€m, m ∈ b β†’ m++ ∈ b) ∧ (βˆ€m, (m ∈ a ∧ m β‰  n) β†’ m++ ∈ a) :=
  elim_choice% [A] by
    ... a proof that uses A ...

which would tell it to unfold A while trying to eliminate uses of Classical.choice, which it will be able to do because the goal is a proposition. This would simulate the conservative extension idea you mentioned @Yusuke Inoue, but restricted to a Prop context.

Yusuke Inoue (Jul 18 2025 at 13:54):

Kyle Miller said:

Maybe a unique_choice axiom would be a weaker assumption (at the cost of adding a new axiom!) but practically it's usually best to avoid any kind of choice at all if it's possible to construct the objects under consideration.

I have just realized that Classical.EM is weaker than Classical.choicebut there's no additional axiom for it, the fact shows that usually 'the cost of adding a new axiom' is seen to be expensive.

Kyle Miller (Jul 18 2025 at 13:57):

Re this point about a missing recursion principle: it's the Calculus of Constructions, not the Calculus of Things We Proved That Exist :-)

Kyle Miller (Jul 18 2025 at 14:06):

I want to be clear that I'm not saying anything about whether it's worth studying which axioms are needed for what or not.

You're certainly free to add your own unique choice axiom!

If you want to be safe about it, you could define it as an opaque definition using Classical.choice, and then treat this as an axiom.

Yusuke Inoue (Jul 18 2025 at 14:16):

Thanks! I think I've got a lot from these replies.


Last updated: Dec 20 2025 at 21:32 UTC