Zulip Chat Archive

Stream: mathlib4

Topic: Why Nat.find is computable, when Wellfounded.fix isn't?


Paradoxy (Oct 15 2025 at 22:45):

I cannot understand this piece of code

protected def findX : { n // p n   m < n, ¬p m } :=
  @WellFounded.fix _ (fun k => ( n < k, ¬p n)  { n // p n   m < n, ¬p m }) lbp (wf_lbp H)
    (fun m IH al =>
      if pm : p m then m, pm, al
      else
        have :  n  m, ¬p n := fun n h =>
          Or.elim (Nat.lt_or_eq_of_le h) (al n) fun e => by rw [e]; exact pm
        IH _ rfl, this fun n h => this n <| Nat.le_of_succ_le_succ h)
    0 fun _ h => absurd h (Nat.not_lt_zero _)

From import Mathlib.Data.Nat.Find.

While WellFounded.fix is noncomputable, somehow findX is accepted as computable? Why?

Aaron Liu (Oct 15 2025 at 23:06):

If you import batteries then it becomes computable

Aaron Liu (Oct 15 2025 at 23:06):

probably there's a better way to write the findX without explicitly referencing Wellfounded.fix

Paradoxy (Oct 15 2025 at 23:11):

Aaron Liu said:

If you import batteries then it becomes computable

Sorry, I don't follow. Let me make my example even more mininal, please explain the behaviour we observe:

def e1 (p : Type*  Prop) (ep :  a, p a) := ep.choose
def e2 (p : Type  Prop) (ep :  a, p a) := ep.choose
noncomputable def e3 (p : Nat  Prop) (ep :  a, p a) := ep.choose -- Why do I have to make it noncomputable?!
noncomputable def e4 (t : Type)(p : t  Prop) (ep :  a, p a) := ep.choose -- Why do I have to make it noncomputable?!
noncomputable def e5 (t : Type*)(p : t  Prop) (ep :  a, p a) := ep.choose -- Why do I have to make it noncomputable?!

Surely I shouldn't import batteries for this, no? I expect everything here to be noncomputable. yet e1 and e2 are computable just fine, despite the fact that I am literally using Classical.choose, a noncomputable function.

Aaron Liu (Oct 15 2025 at 23:17):

not sure if this is good

/-- Find the smallest `n` satisfying `p n`. Returns a subtype. -/
protected def findX : { n // p n   m < n, ¬p m } :=
  go H 0 fun _ h => absurd h (Nat.not_lt_zero _)
where
  go (H :  n, p n) (k : Nat) (hk :  n < k, ¬p n) : { n // p n   m < n, ¬p m } :=
    if pk : p k then k, pk, hk
    else
      have :  n  k, ¬p n := fun n h =>
        Or.elim (Nat.lt_or_eq_of_le h) (hk n) fun e => by rw [e]; exact pk
      go H (k + 1) fun n h => this n <| Nat.le_of_succ_le_succ h
  termination_by (wf_lbp H).wrap k
  decreasing_by exact rfl, this

Aaron Liu (Oct 15 2025 at 23:18):

Paradoxy said:

Aaron Liu said:

If you import batteries then it becomes computable

Sorry, I don't follow. Let me make my example even more mininal, please explain the behaviour we observe:

def e1 (p : Type*  Prop) (ep :  a, p a) := ep.choose
def e2 (p : Type  Prop) (ep :  a, p a) := ep.choose
noncomputable def e3 (p : Nat  Prop) (ep :  a, p a) := ep.choose -- Why do I have to make it noncomputable?!
noncomputable def e4 (t : Type)(p : t  Prop) (ep :  a, p a) := ep.choose -- Why do I have to make it noncomputable?!
noncomputable def e5 (t : Type*)(p : t  Prop) (ep :  a, p a) := ep.choose -- Why do I have to make it noncomputable?!

Surely I shouldn't import batteries for this, no? I expect everything here to be noncomputable. yet e1 and e2 are computable just fine, despite the fact that I am literally using Classical.choose, a noncomputable function.

no batteries specifically does WellFounded.fix along with some other functions (Acc.ndrec, Acc.ndrecOn, WellFounded.fixF, WellFounded.fix) in Batteries/WF by writing @[csimp] lemmas for them

Paradoxy (Oct 15 2025 at 23:22):

Aaron Liu said:

not sure if this is good

/-- Find the smallest `n` satisfying `p n`. Returns a subtype. -/
protected def findX : { n // p n   m < n, ¬p m } :=
  go H 0 fun _ h => absurd h (Nat.not_lt_zero _)
where
  go (H :  n, p n) (k : Nat) (hk :  n < k, ¬p n) : { n // p n   m < n, ¬p m } :=
    if pk : p k then k, pk, hk
    else
      have :  n  k, ¬p n := fun n h =>
        Or.elim (Nat.lt_or_eq_of_le h) (hk n) fun e => by rw [e]; exact pk
      go H (k + 1) fun n h => this n <| Nat.le_of_succ_le_succ h
  termination_by (wf_lbp H).wrap k
  decreasing_by exact rfl, this

Thanks, this is educational. But can you also tell me why exactly e1 and e2 are computable?

Aaron Liu (Oct 15 2025 at 23:22):

oh that's because types are erased at runtime

Aaron Liu (Oct 15 2025 at 23:22):

so there's no data there

Paradoxy (Oct 15 2025 at 23:29):

Aaron Liu said:

so there's no data there

Can you please tell me where can I learn more about this? To my untrained eyes, if e1 and e2 are computable, then so must be e4 and e5.

Aaron Liu (Oct 15 2025 at 23:31):

it's the same reason you don't have to mark your proofs as noncomputable

Aaron Liu (Oct 15 2025 at 23:31):

there's nothing to compute

Aaron Liu (Oct 15 2025 at 23:32):

but for e3 you compute a Nat and for e4 you compute a t

Paradoxy (Oct 15 2025 at 23:36):

Aaron Liu said:

there's nothing to compute

What are we computing in e4 and e5 exactly that makes things noncomputable, that we don't compute in e1 and e2, making them computable? Sorry if I sound too stupid

Aaron Liu (Oct 15 2025 at 23:37):

we are computing the term of type t

Aaron Liu (Oct 15 2025 at 23:37):

for example, I could specialize t := Nat

Paradoxy (Oct 15 2025 at 23:41):

Aaron Liu said:

for example, I could specialize t := Nat

Thank you very much, I understand it now


Last updated: Dec 20 2025 at 21:32 UTC