Zulip Chat Archive

Stream: mathlib4

Topic: computing quotient choice


Aaron Liu (Jan 23 2026 at 02:54):

docs#Quotient.choice is a version of the axiom of choice. It can be used to decide arbitrary propositions, and therefore has no computational interpretation.

Code to decide arbitrary proposition

In #mathlib4 > A monad for partial computations, there is an implementation for countable choice. The trick to decide arbitrary propositions does not work here because the indexing type is fixed to be Nat. The implementation is included here:
Anthony Vandikas said:

unsafe def Quotient.countableChoice_impl {α : Nat  Type*} {S :  i, Setoid (α i)}
    (f :  i, Quotient (S i)) :
    @Quotient ( i, α i) (by infer_instance) :=
  Quotient.lift₂
    (fun z s  fun | .zero => z | .succ n => s n)
    (fun z₁ s₁ z₂ s₂ h₁ h₂  by
      apply sound
      rintro _|n
      · apply h₁
      · apply h₂)
    (f 0)
    (countableChoice_impl (fun n  f n.succ))

@[implemented_by Quotient.countableChoice_impl]
def Quotient.countableChoice {α : Nat  Type*} {S :  i, Setoid (α i)}
    (f :  i, Quotient (S i)) :
    @Quotient ( i, α i) (by infer_instance) :=
  Quotient.choice f

So countable choice is implemented using only safe functions, but there is now a termination issue. It's impossible to prove termination. But I don't see how I can get this to run forever either, so maybe it is sound?

There is also an easier way to implement it, like this:

unsafe def Quotient.countableChoice_impl {α : Nat  Type*} {S :  i, Setoid (α i)}
    (f :  i, Quotient (S i)) :
    @Quotient ( i, α i) (by infer_instance) :=
  fun i  (f i).unquot

An analogous implementation is not sound for Quotient.choice. Is this version sound?

What about dependent choice? The statement of dependent choice looks like this:

import Mathlib

def Quotient.dependentChoice {α : Sort*} {s : Setoid α} (f : α  Quotient s) (init : α) :
    Trunc { seq :   α // seq 0 = init   n, f (seq n) = Quotient.mk s (seq (n + 1)) } :=
  sorry

This also seems to be implementable. But I also thought Quotient.choice was implementable until I saw a counterexample. So how can I tell if a proposed implementation is sound?

Eric Wieser (Jan 23 2026 at 03:20):

For what it's worth you can drop the unsafe above with partial:

import Mathlib

instance {α :   Type u_1} {S : (i : )  Setoid (α i)} [i : Nonempty ((i : )  Quotient (S i))] :
    Nonempty (@Quotient ( i, α i) inferInstance) := i.map Quotient.choice

partial def Quotient.countableChoice_impl {α : Nat  Type*} {S :  i, Setoid (α i)}
    (f :  i, Quotient (S i)) :
    @Quotient ( i, α i) (by infer_instance) :=
  Quotient.lift₂
    (fun z s  fun | .zero => z | .succ n => s n)
    (fun z₁ s₁ z₂ s₂ h₁ h₂  by
      apply sound
      rintro ⟨_|n
      · apply h₁
      · apply h₂)
    (f 0)
    (countableChoice_impl (fun n  f n.succ))

@[implemented_by Quotient.countableChoice_impl]
def Quotient.countableChoice {α : Nat  Type*} {S :  i, Setoid (α i)}
    (f :  i, Quotient (S i)) :
    @Quotient ( i, α i) (by infer_instance) :=
  Quotient.choice f

Eric Wieser (Jan 23 2026 at 03:21):

But I couldn't persuade partial_fixpoint to work

Anthony Vandikas (Jan 23 2026 at 06:26):

Aaron Liu said:

What about dependent choice?

Here is a quick and dirty implementation:

instance {α : Sort*} {s : Setoid α} (f : α  Quotient s) (init : α)
    : Nonempty (Trunc { seq : Nat  α //
        seq 0 = init 
         n, f (seq n) = Quotient.mk s (seq (n + 1)) }) := by
  constructor
  apply Trunc.mk
  use Nat.rec init (fun _ x  (f x).out)
  simp only [Quotient.out_eq, implies_true, and_true]
  rfl

partial def Quotient.dependentChoice {α : Sort*} {s : Setoid α} (f : α  Quotient s) (init : α) :
    Trunc { seq : Nat  α // seq 0 = init   n, f (seq n) = Quotient.mk s (seq (n + 1)) } :=
  Quotient.recOnSubsingleton (motive := fun a  a = f init  _) (f init) (fun a h 
  Quotient.recOnSubsingleton (Quotient.dependentChoice f a) (fun s 
    {
      val
        | 0 => init
        | n + 1 => s.val n
      property := by
        simp only [true_and]
        intro n
        cases n
        · simp only [s.property.1, h]
        · simp only [s.property.2]
    })) rfl

I have not tested that this actually works, which hinges onrecOnSubsingleton being "compiled away" so that the recursive call is essentially done lazily.

My understanding is that anything partial is guaranteed to be "sound" in the sense that you get runtime type preservation, but not in the sense that also guarantees termination. So as long as you can give a partial implementation of whatever axiom you need and can show (possibly independently of Lean's logic) that it terminates, you are good to go.

(I had originally implemented Quotient.countableChoice_impl using partial. It got changed to unsafe because I copied the body of the partial implementation into the definition of the unsafe one by accident :whoops:)


Last updated: Feb 28 2026 at 14:05 UTC