Zulip Chat Archive
Stream: new members
Topic: What's going on with Prod?
Jakub Nowak (Dec 03 2025 at 18:21):
I've run into such two errors, and I'm pretty bamboozled about what's going on.
inductive VecOrd : Type 1 where
| zero : VecOrd
| succ : VecOrd → VecOrd
inductive VecType : Type where
| zero : VecType
| succ : VecType
def VecOrd.type : VecOrd → VecType
| .zero => .zero
| .succ .. => .succ
/--
error: stuck at solving universe constraint
1 =?= max 1 ?u.588
while trying to unify
Type : Type 1
with
Sort (max 1 ?u.588) : Type (max 1 ?u.588)
-/
#guard_msgs in
def VecT (m : Type → Type) (α : Type) (ord : VecOrd) : Type :=
(type : VecType) × (_ : ord.type = type) ×' ((match type, ord with
| .zero, .zero => Unit
| .succ, .succ o => α × m (VecT m α o)
) : Type)
inductive VecOrd : Type 1 where
| zero : VecOrd
| succ : VecOrd → VecOrd
inductive VecType : Type where
| zero : VecType
| succ : VecType
def VecOrd.type : VecOrd → VecType
| .zero => .zero
| .succ .. => .succ
def VecT (m : Type → Type) (α : Type) (ord : VecOrd) : Type :=
(type : VecType) × (_ : ord.type = type) ×' Unit × ((match type, ord with
| .zero, .zero => Unit
| .succ, .succ o => α × m (VecT m α o)
) : Type)
/--
error: Invalid `⟨...⟩` notation: The expected type `VecT m α VecOrd.zero` is not an inductive type
Note: This notation can only be used when the expected type is an inductive type with a single constructor
-/
#guard_msgs in
def VecT.none : VecT m α .zero :=
⟨.zero, sorry, (), ()⟩
The second one is weird, because this works perfectly fine:
inductive VecOrd : Type 1 where
| zero : VecOrd
inductive VecType : Type where
| zero : VecType
def VecOrd.type : VecOrd → VecType
| .zero => .zero
def VecT (m : Type → Type) (α : Type) (ord : VecOrd) : Type :=
(type : VecType) × (_ : ord.type = type) ×' Unit × ((match type, ord with
| .zero, .zero => Unit
) : Type)
def VecT.none : VecT m α .zero :=
⟨.zero, sorry, (), ()⟩
Anthony Wang (Dec 03 2025 at 19:42):
(Bad suggestion, please ignore)
Anthony Wang (Dec 03 2025 at 19:49):
(deleted)
Aaron Liu (Dec 03 2025 at 19:55):
Anthony Wang said:
For your first error, it type-checks if you use → instead of ×' (which often leads to complicated universe level constraints):
But → and ×' are not the same, they're not even isomorphic
Aaron Liu (Dec 03 2025 at 20:00):
it works if you add a by exact
inductive VecOrd : Type 1 where
| zero : VecOrd
| succ : VecOrd → VecOrd
inductive VecType : Type where
| zero : VecType
| succ : VecType
def VecOrd.type : VecOrd → VecType
| .zero => .zero
| .succ .. => .succ
def VecT (m : Type → Type) (α : Type) (ord : VecOrd) : Type :=
(type : VecType) × (_ : ord.type = type) ×' ((by exact match type, ord with
| .zero, .zero => Unit
| .succ, .succ o => α × m (VecT m α o)
) : Type)
Aaron Liu (Dec 03 2025 at 20:04):
the second one seems to be smart unfolding's fault
inductive VecOrd : Type 1 where
| zero : VecOrd
| succ : VecOrd → VecOrd
inductive VecType : Type where
| zero : VecType
| succ : VecType
def VecOrd.type : VecOrd → VecType
| .zero => .zero
| .succ .. => .succ
def VecT (m : Type → Type) (α : Type) (ord : VecOrd) : Type :=
(type : VecType) × (_ : ord.type = type) ×' Unit × ((match type, ord with
| .zero, .zero => Unit
| .succ, .succ o => α × m (VecT m α o)
) : Type)
set_option smartUnfolding false in
def VecT.none {m α} : VecT m α .zero :=
⟨.zero, rfl, (), ()⟩
Jakub Nowak (Dec 03 2025 at 21:24):
Huh, thanks. Though, I still have no idea what's happening. Why by exact .. helps? Should I report any of these as an issue?
Jakub Nowak (Dec 03 2025 at 21:35):
I don't know what smartUnfolding does, but this option sounds like something useful that I shouldn't just turn off. Maybe it's possible to write the definition differently, so there's no need to turn it off?
Jakub Nowak (Dec 03 2025 at 21:49):
I've tried this, and in this case, not even turning off smartUnfolding helps.
inductive VecOrd : Type 1 where
| zero : VecOrd
| succ : VecOrd → VecOrd
inductive VecType : Type where
| zero : VecType
| succ : VecType
def VecOrd.type : VecOrd → VecType
| .zero => .zero
| .succ .. => .succ
mutual
def VecTTyped (m : Type → Type) (α : Type) (ord : VecOrd) {type : VecType} (_ : ord.type = type) : Type :=
match type, ord with
| .zero, .zero => Unit
| .succ, .succ o => α × m (VecT m α o)
def VecT (m : Type → Type) (α : Type) (ord : VecOrd) : Type :=
(type : VecType) × (h : ord.type = type) ×' VecTTyped m α ord h
end
/--
error: Invalid `⟨...⟩` notation: The expected type `VecT m α VecOrd.zero` is not an inductive type
Note: This notation can only be used when the expected type is an inductive type with a single constructor
-/
#guard_msgs in
def VecT.none {m α} : VecT m α .zero :=
⟨.zero, rfl, ()⟩
Aaron Liu (Dec 03 2025 at 21:55):
Jakub Nowak said:
I've tried this, and in this case, not even turning off
smartUnfoldinghelps.
In this case it's used well-founded recursion so there's no way to unfold it definitionally it's irreducible
Jakub Nowak (Dec 03 2025 at 21:55):
In the latter case, it seems like with mutual, function is no longer defeq with it's body.
Aaron Liu (Dec 03 2025 at 21:56):
yeah that's correct
Aaron Liu (Dec 03 2025 at 21:56):
it's not directly because of the mutual recursion
Aaron Liu (Dec 03 2025 at 21:56):
it's because you made it mutual, the calls are not decreasing in the same way, so it used a different strategy to show termination
Aaron Liu (Dec 03 2025 at 21:57):
and that strategy is well-founded recursion and functions defined with well founded recursion are never defeq to their body
Jakub Nowak (Dec 04 2025 at 09:33):
It seems like even with structural recursion function isn't defeq with body.
import Lean
inductive VecOrd : Type 1 where
| zero : VecOrd
| succ : VecOrd → VecOrd
inductive VecType : Type where
| zero : VecType
| succ : VecType
def VecOrd.type : VecOrd → VecType
| .zero => .zero
| .succ .. => .succ
def VecT (m : Type → Type) (α : Type) (ord : VecOrd) : Type :=
(type : VecType) × (_ : ord.type = type) ×' Unit × ((match type, ord with
| .zero, .zero => Unit
| .succ, .succ o => α × m (VecT m α o)
) : Type)
termination_by structural ord
/--
error: Tactic `rfl` failed: The left-hand side
VecT m α ord
is not definitionally equal to the right-hand side
(type : VecType) ×
(x : ord.type = type) ×'
Unit ×
match type, ord, x with
| VecType.zero, VecOrd.zero, x => Unit
| VecType.succ, o.succ, x => α × m (VecT m α o)
m : Type → Type
α : Type
ord : VecOrd
⊢ VecT m α ord =
((type : VecType) ×
(x : ord.type = type) ×'
Unit ×
match type, ord, x with
| VecType.zero, VecOrd.zero, x => Unit
| VecType.succ, o.succ, x => α × m (VecT m α o))
-/
#guard_msgs in
theorem VecT.eq_def' : VecT m α ord = ((type : VecType) × (_ : ord.type = type) ×' Unit × ((match type, ord with
| .zero, .zero => Unit
| .succ, .succ o => α × m (VecT m α o)
) : Type)) := by
rfl
Aaron Liu (Dec 04 2025 at 11:10):
I think you have to case on ord
Jakub Nowak (Dec 04 2025 at 11:13):
Aaron Liu said:
I think you have to case on
ord
Doesn't help.
inductive VecOrd : Type 1 where
| zero : VecOrd
| succ : VecOrd → VecOrd
inductive VecType : Type where
| zero : VecType
| succ : VecType
def VecOrd.type : VecOrd → VecType
| .zero => .zero
| .succ .. => .succ
def VecT (m : Type → Type) (α : Type) (ord : VecOrd) : Type :=
(type : VecType) × (_ : ord.type = type) ×' Unit × ((match type, ord with
| .zero, .zero => Unit
| .succ, .succ o => α × m (VecT m α o)
) : Type)
termination_by structural ord
theorem VecT.eq_def' : VecT m α ord = ((type : VecType) × (_ : ord.type = type) ×' Unit × ((match type, ord with
| .zero, .zero => Unit
| .succ, .succ o => α × m (VecT m α o)
) : Type)) := by
cases ord
· rfl
· rfl
Aaron Liu (Dec 04 2025 at 11:15):
and maybe also turn off smart unfolding?
Jakub Nowak (Dec 04 2025 at 11:16):
Aaron Liu said:
and maybe also turn off smart unfolding?
Still doesn't work.
Jakub Nowak (Dec 04 2025 at 11:16):
Does smartUnfolding changes what is defeq and what isn't?
Aaron Liu (Dec 04 2025 at 11:17):
hmm weird
Aaron Liu (Dec 04 2025 at 11:20):
oh it's a naturality diamond I think
Aaron Liu (Dec 04 2025 at 11:21):
yeah and it also fails to prove the equation automatically
Aaron Liu (Dec 04 2025 at 11:21):
I think this is a bug maybe?
Jakub Nowak (Dec 04 2025 at 11:22):
Hm, should I post it on #lean4 , or directly on Github issues?
Jakub Nowak (Dec 04 2025 at 11:24):
I've tried debugging and reading Lean's code a little, but with my little knowledge of the internals I didn't make any progress at understanding why it's failing.
Jakub Nowak (Dec 04 2025 at 11:57):
Here's manual proof, maybe that can help in pinpointing the problem:
inductive VecOrd : Type 1 where
| zero : VecOrd
| succ : VecOrd → VecOrd
inductive VecType : Type where
| zero : VecType
| succ : VecType
def VecOrd.type : VecOrd → VecType
| .zero => .zero
| .succ .. => .succ
def VecT (m : Type → Type) (α : Type) (ord : VecOrd) : Type :=
(type : VecType) × (_ : ord.type = type) ×' Unit × ((match type, ord with
| .zero, .zero => Unit
| .succ, .succ o => α × m (VecT m α o)
) : Type)
set_option pp.notation false
theorem VecT.eq_def' : VecT m α ord = ((type : VecType) × (_ : ord.type = type) ×' Unit × ((match type, ord with
| .zero, .zero => Unit
| .succ, .succ o => α × m (VecT m α o)
) : Type)) := by
cases ord
· conv => lhs; delta VecT
simp only
congr
funext type
congr
funext h
congr
split
· rfl
· grind only
· conv => lhs; delta VecT
simp only
congr
funext type
congr
funext h
congr
split
· grind only
. congr
conv => rhs; delta VecT
grind only
Niklas Halonen (Dec 07 2025 at 08:54):
I'm not sure how termination_by structural ord works/should work, but I don't think it can get rid of well-founded recursion as the inner match is inside the sigma over the Eq. Anyways it looks like the following works (with termination_by ord and gives your eq_def':
inductive VecOrd : Type 1 where
| zero : VecOrd
| succ : VecOrd → VecOrd
inductive VecType : Type where
| zero : VecType
| succ : VecType
def VecOrd.type : VecOrd → VecType
| .zero => .zero
| .succ .. => .succ
def VecT (m : Type → Type) (α : Type) (ord : VecOrd) : Type :=
(type : VecType) × (_ : ord.type = type) ×' Unit × ((match type, ord with
| .zero, .zero => Unit
| .succ, .succ o => α × m (VecT m α o)
) : Type)
termination_by ord
/-
@[irreducible] def VecT : (Type → Type) → Type → VecOrd → Type :=
fun m α =>
VecT._proof_1.fix fun ord a =>
(type : VecType) ×
(x : ord.type = type) ×'
Unit ×
(match (motive :=
(type : VecType) →
(ord : VecOrd) →
ord.type = type → ((y : VecOrd) → (invImage (fun x => x) sizeOfWFRel).1 y ord → Type) → Type)
type, ord, x with
| VecType.zero, VecOrd.zero, x => fun x => Unit
| VecType.succ, o.succ, x => fun x => α × m (x o ⋯))
a
-/
#print VecT
/-
∀ (m : Type → Type) (α : Type) (ord : VecOrd),
VecT m α ord =
((type : VecType) ×
(x : ord.type = type) ×'
Unit ×
match type, ord, x with
| VecType.zero, VecOrd.zero, x => Unit
| VecType.succ, o.succ, x => α × m (VecT m α o))
-/
#check VecT.eq_def
theorem VecT.eq_def' : VecT m α ord = ((type : VecType) × (_ : ord.type = type) ×' Unit × ((match type, ord with
| .zero, .zero => Unit
| .succ, .succ o => α × m (VecT m α o)
) : Type)) := eq_def _ _ _
Tested on 4.24.0 and 4.26.0-rc2
Aaron Liu (Dec 07 2025 at 11:09):
but unfortunately you lose defeqs
Kyle Miller (Dec 07 2025 at 13:39):
I haven't seen anyone mention that structural recursion isn't able to generate equation lemmas. That could be indicating a significant problem. Plus, if it could generate the equation lemma, any eq_def lemma would be already proved.
import Lean
inductive VecOrd : Type 1 where
| zero : VecOrd
| succ : VecOrd → VecOrd
inductive VecType : Type where
| zero : VecType
| succ : VecType
def VecOrd.type : VecOrd → VecType
| .zero => .zero
| .succ .. => .succ
def VecT (m : Type → Type) (α : Type) (ord : VecOrd) : Type :=
Sigma.{0,0} fun type : VecType =>
PSigma.{0,1} fun _ : ord.type = type =>
match type, ord with
| .zero, .zero => Unit
| .succ, .succ o => α × m (VecT m α o)
#print VecT.eq_1
/-
Failed to realize constant VecT.eq_1:
failed to generate equational theorem for `VecT`
no progress at goal
m : Type → Type
α : Type
ord : VecOrd
r : VecOrd.below ord := (VecOrd.brecOn.go ord fun ord f =>
(type : VecType) ×
(x : ord.type = type) ×'
(match (motive := (type : VecType) → (ord : VecOrd) → ord.type = type → VecOrd.below ord → Type) type, ord,
x with
| VecType.zero, VecOrd.zero, x => fun x => Unit
| VecType.succ, o.succ, x => fun x => α × m x.1)
f).2
⊢ ((type : VecType) ×
(x : ord.type = type) ×'
(match (motive :=
(type : VecType) →
(ord : VecOrd) → ord.type = type → VecOrd.rec PUnit (fun a a_ih => Type ×' a_ih) ord → Type)
type, ord, x with
| VecType.zero, VecOrd.zero, x => fun x => Unit
| VecType.succ, o.succ, x => fun x => α × m x.1)
r) =
((type : VecType) ×
(x : ord.type = type) ×'
match type, ord, x with
| VecType.zero, VecOrd.zero, x => Unit
| VecType.succ, o.succ, x => α × m (VecT m α o))
-/
(I also haven't seen anyone mention that it's Sigma, not Prod.)
Last updated: Dec 20 2025 at 21:32 UTC