Zulip Chat Archive
Stream: general
Topic: Cardinality model incompatible with Lean compiler
Violeta Hernández (Feb 24 2025 at 23:32):
Something funny I figured out, the axiom α ≃ β → α = β is incompatible with native_decide:
import Mathlib.Data.Setoid.Basic
axiom equiv_of_eq {α β : Type} (f : α ≃ β) : α = β
def Nat' := Quotient (Setoid.ker fun x : Nat ↦ x - 1)
@[simp] theorem mk_zero : (⟦0⟧ : Nat') = ⟦1⟧ := Quotient.sound rfl
def equivNat : Nat' ≃ Nat where
toFun x := x.lift _ fun _ _ ↦ id
invFun x := ⟦x + 1⟧
left_inv x := x.inductionOn fun x ↦ x.recOn (by simp) (by simp)
right_inv x := by simp
def castNat : Nat' → Nat := cast (equiv_of_eq equivNat)
theorem contradiction : False := by
have : castNat ⟦0⟧ ≠ castNat ⟦1⟧ := by native_decide
simpa
-- 'contradiction' depends on axioms: [equiv_of_eq, propext, Lean.ofReduceBool, Quot.sound]
#print axioms contradiction
This is pretty inconsequential all things considered; there's easier ways to prove False using native_decide (e.g. bad implemented_by tags). But I guess this one more pitfall with compiled Lean to be aware of.
Violeta Hernández (Feb 24 2025 at 23:35):
Relating to this: are we absolutely sure cast can't be used for evil in "core" Lean? Even if that's just crashing the VM through a bad #eval. Do we always know that equal types must have the exact same VM representation?
Mario Carneiro (Feb 25 2025 at 00:19):
we get that property from the other extremal model, where you tag all types so that different inductives are distinct
Mario Carneiro (Feb 25 2025 at 00:21):
in that model, equality of types implies that they have the same construction, although there are some fun exceptions to this rule involving the inductive types which are not injective by cantor's theorem
Mario Carneiro (Feb 25 2025 at 00:23):
But a full answer to this question involves proving the correctness of type erasure, a la this paper
Aaron Liu (Sep 06 2025 at 15:48):
having fun
import Mathlib.Logic.Small.Defs
import Mathlib.Data.Setoid.Basic
def Nat' := Quotient (Setoid.ker fun x : Nat ↦ x - 1)
@[simp] theorem mk_zero : (⟦0⟧ : Nat') = ⟦1⟧ := Quotient.sound rfl
def equivNat : Nat' ≃ Nat where
toFun x := x.lift _ fun _ _ ↦ id
invFun x := ⟦x + 1⟧
left_inv x := x.inductionOn fun x ↦ x.recOn (by simp) (by simp)
right_inv x := by simp
theorem Shrink.eq_of_equiv {α : Type u} {β : Type v} [Small.{w} α] [Small.{w} β]
(equiv : α ≃ β) : Shrink.{w} α = Shrink.{w} β := by
unfold Shrink
have hu (x : Type w) : Nonempty (α ≃ x) = Nonempty (β ≃ x) :=
propext ⟨Nonempty.map equiv.symm.trans, Nonempty.map equiv.trans⟩
simp [hu]
def castNat : Shrink.{0} Nat' → Shrink.{0} Nat := cast (Shrink.eq_of_equiv equivNat)
theorem contradiction : False := by
have : (equivShrink _).symm (castNat (equivShrink _ ⟦0⟧)) ≠
(equivShrink _).symm (castNat (equivShrink _ ⟦1⟧)) := by native_decide
simpa
/- 'contradiction' depends on axioms:
[propext, Classical.choice, Lean.ofReduceBool, Lean.trustCompiler, Quot.sound] -/
#print axioms contradiction
Aaron Liu (Sep 06 2025 at 15:50):
see the implemented_by which makes this happen,
https://github.com/leanprover-community/mathlib4/blob/5123e3ed3fb8a3d1c4393ebe5ef8fbf543d8aa1a/Mathlib/Logic/Small/Defs.lean#L44-L55
Weiyi Wang (Sep 06 2025 at 15:59):
What should be my takeaway from the example above? I was previously told that I can't trust native_decide because it can be affected by external plugins, but that left me impression that within the boundary of a controlled set of dependencies (lean core & mathlib) it is fairly correct. But this one implies a different story
Aaron Liu (Sep 06 2025 at 16:00):
the takeaway is that for equivShrinkImpl to be sound we need to make Shrink opaque
Cameron Zwarich (Sep 06 2025 at 16:10):
Mario Carneiro said:
But a full answer to this question involves proving the correctness of type erasure, a la this paper
Would proving type erasure correct actually answer the question of which axioms consistent with Lean's type theory are also consistent with the "model" provided by Lean's compiler?
Cameron Zwarich (Sep 06 2025 at 16:15):
Aaron Liu said:
see the
implemented_bywhich makes this happen,
https://github.com/leanprover-community/mathlib4/blob/5123e3ed3fb8a3d1c4393ebe5ef8fbf543d8aa1a/Mathlib/Logic/Small/Defs.lean#L44-L55
There is probably some value in having native_decide distinguish between implemented_by/extern/unsafe/etc. decls in Lean's core libraries (maybe even just Init) and those outside of it, then only allowing the use of the former by default without additional options (possibly including an option to print the ones that were actually used).
Eric Wieser (Sep 06 2025 at 17:07):
Aaron Liu said:
the takeaway is that for
equivShrinkImplto be sound we need to makeShrinkopaque
Or rather, a sigma type combining Shrink and the Equiv?
Aaron Liu (Sep 06 2025 at 17:13):
Eric Wieser said:
Aaron Liu said:
the takeaway is that for
equivShrinkImplto be sound we need to makeShrinkopaqueOr rather, a sigma type combining
Shrinkand theEquiv?
of course, otherwise we wouldn't be able to get the Equiv
Cameron Zwarich (Sep 06 2025 at 23:49):
Another example showing the compiler's incompatibility with the cardinality model is redefining Nat by copying its definition as an inductive. The compiler's special representation for Nat will not be used for your own type. IIRC Idris will use the builtin Nat representation for any type that looks close enough to Nat (and we could do this too), but this will never solve the incompatibility, since you can e.g. always add a field whose size depends on whether FLT is true.
Violeta Hernández (Sep 07 2025 at 00:48):
Aaron Liu said:
the takeaway is that for
equivShrinkImplto be sound we need to makeShrinkopaque
Is this something we should PR?
Robin Arnez (Sep 07 2025 at 00:51):
Yeah, probably. Might also be good to make Shrink a structure at the same time, for good measure :-)
Kim Morrison (Sep 07 2025 at 10:39):
I would be happier about removing this implemented_by from Mathlib.
Robin Arnez (Sep 07 2025 at 14:45):
Hmm you're right, this is the only occurrence of implemented_by in mathlib that's not on an opaque. The way I see it, we only need implemented_by here because of a missing feature (notably lean4#9508), so the implemented_by will go away eventually. In the meantime we can probably use opaque to fix the problem or leave it to anyone needing it to implement it themselves.
Cameron Zwarich (Sep 07 2025 at 15:05):
Kim Morrison said:
I would be happier about removing this implemented_by from Mathlib.
I would be happier if Mathlib had no uses of implemented_by. Between adding missing csimps, improved compiler performance, and new features, how realistic is this future?
Weiyi Wang (Sep 07 2025 at 15:10):
I only see 4 implemented_by by code search, so I guess... not far away?
Cameron Zwarich (Sep 07 2025 at 15:18):
To me it looks like this:
equivShrinkinMathlib/Logic/Small/Defs.lean. This will be solved by #9508, which should happen next quarter (because it's needed to make a goodIteratorAPI).memoFixinMathlib/Util/MemoFix.lean. The real problem here is the use of very unsafeunsafecode (which also usesunsafeBaseIO), although its usage is restricted to tactics. It's only used byMathlib/Lean/Expr/ReplaceRec.lean, but I don't see a way to do this with safe code without slightly changing the interface.Float.valUnsafeandFloat.mkUnsafeinMathlib/Util/CompileInductive.lean. The compiler will support recursors for arbitrary inductives soonish, so this file will just be deleted.
Kim Morrison (Sep 08 2025 at 01:15):
reverts the change introducing this most recent invalid implemented_by.
Eric Wieser (Sep 08 2025 at 01:51):
(lean4#9508 is the right link for Cameron's message)
Dexin Zhang (Sep 08 2025 at 14:48):
May I ask why Shrink can still be computable, while its definition uses Classical.choose?
Robin Arnez (Sep 08 2025 at 15:03):
Well, the definition couldn't be computable in its current state (as figured out in this thread) but if Shrink would be opaque then it is fine to assert certain things about its runtime behavior, in particular you can assume it that Shrink alpha has the same runtime representation as alpha
Robin Arnez (Sep 08 2025 at 15:16):
And after lean4#9508, you could use Classical.choose but you wouldn't use the constructor / projection but instead an autogenerated function to convert between Shrink alpha and alpha like
def_type_with_rep Shrink.{v} (α : Type u) [h : Small.{v} α] where
absType := h.equiv_small.choose
repType := α
toRep := h.equiv_small.choose_spec.some.invFun
fromRep := h.equiv_small.choose_spec.some.toFun
fromRep_toRep := by simp
toRep_fromRep := by simp
#check Shrink.ofRep -- α -> Small.{v} α
#check Shrink.rep -- Small.{v} α -> α
Dexin Zhang (Sep 08 2025 at 15:32):
But Shrink as a function returns Type is indeed computable even after #29419... Do you mean we can write Shrink computably, but can never computably construct an element of it?
Robin Arnez (Sep 08 2025 at 15:34):
Computability of Shrink itself is not a concern; types are irrelevant in Lean; the question is whether you'd be able to create a value Shrink α from a value of α. This is possible with an @[implemented_by] and should be possible without after some variant of lean4#9508 has been implemented.
Dexin Zhang (Sep 08 2025 at 15:39):
I see. So functions returning Types (like Shrink) are actually erased in runtime?
William Sørensen (Sep 28 2025 at 09:51):
Hi sorry for introducing this instance of False through native decide :man_facepalming:. I was wondering though how would the new proposal override_repr solve this? I tried just skimming the gh thread but I couldn’t find an example. Could someone link me this? It would just be really nice to see because it would be super useful for coinductives through QPF
William Sørensen (Sep 28 2025 at 10:14):
Wait lol i am completely blind. Ok I see now how it would be implemented. Do we know if / when this is planned to be added to lean?
James E Hanson (Sep 30 2025 at 18:40):
Mario Carneiro said:
in that model, equality of types implies that they have the same construction, although there are some fun exceptions to this rule involving the inductive types which are not injective by cantor's theorem
Is there a reference that talks about this failure of injectivity for inductive types?
Aaron Liu (Sep 30 2025 at 18:44):
Kyle Miller said:
Here's an example:
import Mathlib inductive IsntInj (f : Type → Prop) : Type example : ¬ Function.Injective IsntInj := Function.cantor_injective IsntInj
Here's an example
Kevin Buzzard (Oct 01 2025 at 16:39):
Huh! Lean is Ok with that inductive being in Type rather than Type 1?
Violeta Hernández (Oct 01 2025 at 17:05):
Kevin Buzzard said:
Huh! Lean is Ok with that inductive being in
Typerather thanType 1?
None of the constructors (of which there are none) make use of f, which is why I think this is allowed.
Kevin Buzzard (Oct 01 2025 at 20:04):
Huh,
inductive Thing (f : Type 7 → Type 2) : Type 4
is also fine (I would have used Type 37 but apparently such silly ideas are now banned).
Aaron Liu (Oct 01 2025 at 20:07):
not banned, just needs a bit of work
inductive Thing (f : Type (25 + 12) → Type 2) : Type 4
or, if you actually want 37 we can do
set_option maxUniverseOffset 255 in
inductive Thing (f : Type 37 → Type 2) : Type 4
Aaron Liu (Oct 01 2025 at 20:12):
I tried adding some constructors and it still works?
Robin Arnez (Oct 01 2025 at 21:54):
Well, for something like
inductive Thing (f : Type 30 → Type 2) : Type 4 where
| hello (x : f PUnit)
you could've also done
inductive OtherThing (α : Type 2) : Type 4 where
| hello (x : α)
def Thing (f : Type 30 → Type 2) : Type 4 := OtherThing (f PUnit)
The parameters of the inductive simply aren't put into the universe restrictions because you can model them indirectly.
Violeta Hernández (Oct 01 2025 at 22:31):
Wait, why is the universe number capped now?
Floris van Doorn (Oct 01 2025 at 22:34):
Violeta Hernández said:
Wait, why is the universe number capped now?
It's not, see the set_option maxUniverseOffset 255 above.
It's just that universe levels are stored in unary, so something like Type 1000000 creates a universe expression with a million Level.succ's, so they are capped by default.
Trebor Huang (Oct 03 2025 at 03:24):
Kevin Buzzard said:
Huh! Lean is Ok with that inductive being in
Typerather thanType 1?
Even if we are to take the universe level into account, it should be Type too. Notice that List (A : Type) : Type is valid, and Type is at the same universe level as Type -> Prop. If we raise the universe level here we're basically in an ultra-predicative logic where most inductive constructions give you proper classes instead of sets.
Last updated: Dec 20 2025 at 21:32 UTC