Zulip Chat Archive
Stream: lean4
Topic: Phantom decreasing measure and monadic termination
Leni Aniva (Aug 18 2025 at 20:22):
I want to prove that this terminates:
structure State where
n : Nat
def f : StateM State Nat := do
match (← get).n with
| x+1 =>
set ({n := x} : State)
f
| 0 =>
return 0
I could not use termination_by since the function takes no arguments.
In this case, the variant is the n inside State. Is there a way to add the variant to the arguments of f, without changing its ABI call signature? For example, if I add a Nat, the compiled code would be different.
def f (n : Nat) : StateM State Nat := ...
would compile with an extra argument compared to the f above.
Is there a way to construct a Rust Phantom-like type in Lean that could be used as a variant (so a singleton inductive would not do it), and does not occupy physical memory?
For more complicated monadic algorithms, some termination proofs call for the construction of non-trivial variants. In which case, adding an explicit variant argument would change both the memory signature and runtime of the function.
Ideally the function would look like
def (n : Phantom Nat) : StateM State Nat := ...
decreasing_by n
This doesn't work either since it needs to generate projections:
structure Decr : Prop where
n : Nat
Leni Aniva (Aug 18 2025 at 20:26):
This does not work either:
inductive Flag : Prop where
| intro (n : Nat)
structure State (flag : Flag) where
n : Nat
p : flag = .intro n
def f (flag : Flag) : StateM (State flag) Nat := do
match (← get).n with
| x+1 =>
let flag' := .intro x
set ({n := x, p := rfl} : State flag')
f flag'
| 0 =>
return 0
which shows
fail to show termination for
f
with errors
failed to infer structural recursion:
Cannot use parameter flag:
the type Flag does not have a `.brecOn` recursor
Could not find a decreasing measure.
The basic measures relate at each recursive call as follows:
(<, ≤, =: relation proved, ? all proofs failed, _: no proof attempted)
1) 12:4-11
Please use `termination_by` to specify a decreasing measure.
Aaron Liu (Aug 18 2025 at 20:29):
Well that's because Flag.intro n = Flag.intro m even when n ≠ m
Leni Aniva (Aug 18 2025 at 20:29):
Aaron Liu said:
Well that's because
Flag.intro n = Flag.intro meven whenn ≠ m
so is there no way to make a phantom variant
Aaron Liu (Aug 18 2025 at 20:30):
- You could make it
partial - You could unfold the definition of
StateMand expose a function
Paul Reichert (Aug 18 2025 at 20:34):
The latter might would look like this:
structure State where
n : Nat
@[inline]
def f : StateM State Nat :=
let rec f' (state : State) :=
match _ : state.n with
| x+1 =>
f' {n := x}
| 0 =>
(0, state)
termination_by state.n
f'
Not super nice, but I don't know of a better way in such a case...
Paul Mure (Aug 18 2025 at 20:37):
structure State where
n : Nat
def f : StateM State Nat := fun s =>
match s with
| ⟨0⟩ => (0, s)
| ⟨x + 1⟩ => f ⟨x⟩
termination_by s => s.n
Slightly cleaner, but might get annoying to do when the state is non-trivial. Also someone should confirm if the ABI is the same with this definition.
Paul Mure (Aug 18 2025 at 20:38):
Also you entirely lose the benefit of the do-notation with this.
Leni Aniva (Aug 18 2025 at 21:34):
Paul Mure said:
structure State where n : Nat def f : StateM State Nat := fun s => match s with | ⟨0⟩ => (0, s) | ⟨x + 1⟩ => f ⟨x⟩ termination_by s => s.nSlightly cleaner, but might get annoying to do when the state is non-trivial. Also someone should confirm if the ABI is the same with this definition.
This also wouldn't work with StateRefT
Aaron Liu (Aug 18 2025 at 21:38):
I don't think you can prove that functions with StateRefT terminate by arguing the state is decreasing
Henrik Böving (Aug 18 2025 at 21:39):
That's correct
Leni Aniva (Aug 18 2025 at 21:41):
Aaron Liu said:
I don't think you can prove that functions with
StateRefTterminate by arguing the state is decreasing
Would it be possible with additional assumptions about separation logic?
Henrik Böving (Aug 18 2025 at 21:47):
People have so far not been able to find a sound axiomatization of IO.Ref
Robin Arnez (Aug 18 2025 at 21:50):
Well, more concretely, the naive axiomatization of IO.Ref is simply inconsistent. You can get something consistent if you add the hypothesis that type inside the reference is "small" for some theorems though, e.g. see my attempt at an EIO axiomatization
Leni Aniva (Aug 18 2025 at 22:04):
could type erasure annotations be added to Lean to make phantom variants possible?
Robin Arnez (Aug 18 2025 at 22:10):
If you only care about equation lemmas, then partial_fixpoint might be enough to make it work though there's some stuff needed that currently isn't in the standard library:
open Lean Order
noncomputable instance [Nonempty α] : CCPO (Id α) := inferInstanceAs (CCPO (FlatOrder Classical.ofNonempty))
noncomputable instance [Nonempty ε] : CCPO (EStateM ε σ α) :=
inferInstanceAs (CCPO ((s : σ) → FlatOrder (.error Classical.ofNonempty (Classical.choice ⟨s⟩))))
noncomputable instance [Nonempty ε] : CCPO (EIO ε α) := inferInstanceAs (CCPO (EStateM _ _ _))
noncomputable instance {m : Type u → Type v} [∀ α, PartialOrder (m α)] : PartialOrder (ReaderT ρ m α) := inferInstanceAs (PartialOrder (_ → _))
noncomputable instance {m : Type u → Type v} [∀ α, PartialOrder (m α)] : PartialOrder (StateT ρ m α) := inferInstanceAs (PartialOrder (_ → _))
noncomputable instance {m : Type → Type} [∀ α, PartialOrder (m α)] : PartialOrder (StateRefT' ω σ m α) := inferInstanceAs (PartialOrder (_ → _))
noncomputable instance {m : Type u → Type v} [∀ α, CCPO (m α)] : CCPO (ReaderT ρ m α) := inferInstanceAs (CCPO (_ → _))
noncomputable instance {m : Type u → Type v} [∀ α, CCPO (m α)] : CCPO (StateT ρ m α) := inferInstanceAs (CCPO (_ → _))
noncomputable instance {m : Type → Type} [∀ α, CCPO (m α)] : CCPO (StateRefT' ω σ m α) := inferInstanceAs (CCPO (_ → _))
instance [Nonempty ε] : MonoBind (EStateM ε σ) where
bind_mono_left {_ _ a₁ a₂ f} h s := by
specialize h s
simp only [bind, EStateM.bind]
generalize a₁ s = a₁ at h
generalize a₂ s = a₂ at h
cases h
· exact .bot
· exact .refl
bind_mono_right {_ _ a f₁ f₂} h s := by
simp only [bind, EStateM.bind]
split
· apply h
· exact .refl
instance [Monad m] [∀ α, PartialOrder (m α)] [MonoBind m] : MonoBind (ReaderT ρ m) where
bind_mono_left h s := MonoBind.bind_mono_left (h s)
bind_mono_right h s := MonoBind.bind_mono_right (fun x => h x s)
instance [Monad m] [∀ α, PartialOrder (m α)] [MonoBind m] : MonoBind (StateT ρ m) where
bind_mono_left h s := MonoBind.bind_mono_left (h s)
bind_mono_right h _ := MonoBind.bind_mono_right (fun x => h x.1 x.2)
instance [Monad m] [∀ α, PartialOrder (m α)] [MonoBind m] : MonoBind (StateRefT' ω σ m) :=
inferInstanceAs (MonoBind (ReaderT _ _))
structure State where
n : Nat
def f : StateT State Option Nat := do
match (← get).n with
| x+1 =>
set ({n := x} : State)
f
| 0 =>
return 0
partial_fixpoint
Alex Keizer (Aug 18 2025 at 22:20):
Leni Aniva said:
could type erasure annotations be added to Lean to make phantom variants possible?
I've not read through the entire thread, but is Erased what you're looking for here?
Last updated: Dec 20 2025 at 21:32 UTC