Zulip Chat Archive

Stream: Is there code for X?

Topic: Lifting a quotient function


Robin Arnez (Mar 06 2025 at 14:19):

import Mathlib.Data.Quot

noncomputable def Quotient.liftFun {s : Setoid α} (f : (β  α)  γ) : ( f₁ f₂, ( x, f₁ x  f₂ x)  f f₁ = f f₂) 
    (β  Quotient s)  γ := fun _ q => f (fun a => (q a).out)

theorem Quotient.liftFun_mk {s : Setoid α} (f : (β  α)  γ) (h :  f₁ f₂, ( x, f₁ x  f₂ x)  f f₁ = f f₂) (g : β  α) :
    Quotient.liftFun f h (fun x => Quotient.mk s (g x)) = f g := by
  unfold liftFun
  apply h
  intro x
  exact Quotient.mk_out (g x)

Is there a computable version of this function? I feel like this is a safe operation but I haven't seen any computable versions...

Aaron Liu (Mar 06 2025 at 14:31):

No, this is equivalent to a version of the axiom of choice. You can still make it computable, though.

import Mathlib.Data.Quot

unsafe def Quotient.liftFunImpl {s : Setoid α} (f : (β  α)  γ) : ( f₁ f₂, ( x, f₁ x  f₂ x)  f f₁ = f f₂) 
    (β  Quotient s)  γ := fun _ q => f (fun a => (q a).unquot)

@[implemented_by Quotient.liftFunImpl]
def Quotient.liftFun {s : Setoid α} (f : (β  α)  γ) : ( f₁ f₂, ( x, f₁ x  f₂ x)  f f₁ = f f₂) 
    (β  Quotient s)  γ := fun _ q => f (fun a => (q a).out)

theorem Quotient.liftFun_mk {s : Setoid α} (f : (β  α)  γ) (h :  f₁ f₂, ( x, f₁ x  f₂ x)  f f₁ = f f₂) (g : β  α) :
    Quotient.liftFun f h (fun x => Quotient.mk s (g x)) = f g := by
  unfold liftFun
  apply h
  intro x
  exact Quotient.mk_out (g x)

Robin Arnez (Mar 06 2025 at 14:43):

Ah, okay, kinda thought that already. I feel like something like this should exist somewhere in mathlib or batteries though (I guess with an unsafe implementation).

Aaron Liu (Mar 06 2025 at 14:53):

Actually, you might be able to bypass choice with some dependent type casting shenanigans. I don't actually have a proof yet.

Robin Arnez (Mar 06 2025 at 14:57):

Huh, okay... I mean ideally liftFun_mk would be defeq but I don't think we will get that.

RustyYato (May 27 2025 at 04:20):

Since this came up in another thread, I'll post it here. Quotient.liftFunImpl is not a sound function! And it is not sound to @[implemented_by Quotient.liftFunImpl] def Quotient.liftFun. Here's the contradiction (adapted from leanprover.zulipchat.com/#narrow/channel/287929-mathlib4/topic/Should.20we.20merge.20.60Quotient.2Eout.60.20and.20.60Quotient.2Eunquot.60.3F/near/390172207):

import Mathlib.Data.Quot

unsafe def Quotient.liftFunImpl {s : Setoid α} (f : (β  α)  γ) : ( f₁ f₂, ( x, f₁ x  f₂ x)  f f₁ = f f₂) 
    (β  Quotient s)  γ := fun _ q => f (fun a => (q a).unquot)

@[implemented_by Quotient.liftFunImpl]
def Quotient.liftFun {s : Setoid α} (f : (β  α)  γ) : ( f₁ f₂, ( x, f₁ x  f₂ x)  f f₁ = f f₂) 
    (β  Quotient s)  γ := fun _ q => f (fun a => (q a).out)

theorem Quotient.liftFun_mk {s : Setoid α} (f : (β  α)  γ) (h :  f₁ f₂, ( x, f₁ x  f₂ x)  f f₁ = f f₂) (g : β  α) :
    Quotient.liftFun f h (fun x => Quotient.mk s (g x)) = f g := by
  unfold liftFun
  apply h
  intro x
  exact Quotient.mk_out (g x)

def choice {α β} [S: Setoid β] (f: i: α, Quotient S): Quotient (piSetoid (α := fun _: α => β)) := by
  apply Quotient.liftFun (β := α) (α := β) (s := S) (γ := Quotient piSetoid) ?_ ?_
  assumption
  exact Quotient.mk _
  intro a b eq
  apply Quot.sound
  assumption

def exoticInstance : Decidable True := by
  apply @Quot.recOnSubsingleton _ _ (fun _ => Decidable True)
  -- lift the identity function
  exact (@choice (α := Trunc Bool) (β := Bool) (S := trueSetoid) id)
  intro f
  replace f: Trunc Bool -> Bool := f
  -- since Trunc is subsingleton, f can only take on one value
  -- but we lifted the identity function, which takes on two values
  apply decidable_of_iff (f (Trunc.mk false) = f (Trunc.mk true))
  simp
  congr 1
  apply Subsingleton.allEq

example : @decide True exoticInstance = true  := .trans (by congr) (decide_true _)
example : @decide True exoticInstance = false := by native_decide

#eval exoticInstance -- isFalse _

The problem here is that Trunc Bool has two computationally distict terms (Trunc.mk true and Trunc.mk false), and by lifting the identity function to Trunc (Trunc Bool -> Bool) via Quotient.liftFun. But this function has impossible properties! For example, f (Trunc.mk false) != f (Trunc.mk true). So it's not even a function really, which allows us to prove false (via native_decide to utilize the bad implemented_by attribute)


Last updated: Dec 20 2025 at 21:32 UTC