Zulip Chat Archive

Stream: Is there code for X?

Topic: Can this be done more elegantly?


Ching-Tsun Chou (Nov 14 2025 at 00:55):

I have a proof whose essence is captured by the Lean code below. The theorem extend_lemma takes a list u and shows that it can be extended with certain properties. I use this theorem to construct an infinite sequence seq of lists which satisfies the properties in seq_lemma. Here's my question: Is there a more elegant way to define seq? Currently the definition uses Classical.choose and I find it not very pleasant to reason about anything defined using Classical.choose. (One has to get the unfolding just right in order to use the result produced by Classical.choose_spec. It took me 5~10 min to do just that in the example below.) Or is there a more advanced way to reason about something defined using Classical.choose that I don't know about?

import Mathlib

variable {α : Type*}

theorem extend_lemma (p q : List α  Prop) (u : List α) :  v : List α, p v  q (u ++ v) := sorry

private noncomputable def seq (p q : List α  Prop) (n : ) : List α :=
  Classical.choose <| extend_lemma p q (List.ofFn (fun k : Fin n  seq p q k)).flatten

theorem seq_lemma (p q : List α  Prop) (n : ) :
    p (seq p q n)  q (List.ofFn (fun k : Fin (n + 1)  seq p q k)).flatten := by
  let r u v := p v  q (u ++ v)
  have : r (List.ofFn (fun k : Fin n  seq p q k)).flatten (seq p q n) := by
    rw [seq]
    exact Classical.choose_spec <| extend_lemma p q (List.ofFn (fun k : Fin n  seq p q k)).flatten
  obtain h1, h2 := this
  refine h1, ?_⟩
  rw [List.ofFn_succ_last]
  simpa

Aaron Liu (Nov 14 2025 at 01:29):

how about like this?

import Mathlib

variable {α : Type*}

theorem extend_lemma (p q : List α  Prop) (u : List α) :  v : List α, p v  q (u ++ v) := sorry

private noncomputable def seq (p q : List α  Prop) (n : ) : List α :=
  Classical.choose <| extend_lemma p q (List.ofFn (fun k : Fin n  seq p q k)).flatten

theorem seq_lemma (p q : List α  Prop) (n : ) :
    p (seq p q n)  q (List.ofFn (fun k : Fin (n + 1)  seq p q k)).flatten := by
  simp_rw [List.ofFn_succ_last, List.flatten_append,
    List.flatten_singleton, Fin.val_last, Fin.coe_castSucc]
  rw [seq]
  generalize_proofs h
  exact h.choose_spec

Jakub Nowak (Nov 14 2025 at 03:09):

∃ v : List α, p v ∧ q (u ++ v) is basically a dependant triple. It's often easier to split this into three separate functions.

Smth like this:

import Mathlib

variable {α : Type*}

noncomputable def extend (p q : List α  Prop) (u : List α) : List α := sorry

theorem extend_lemma_p (p q : List α  Prop) (u : List α) : p (extend p q u) := sorry

theorem extend_lemma_q (p q : List α  Prop) (u : List α) : q (u ++ extend p q u) := sorry

private noncomputable def seq (p q : List α  Prop) (n : ) : List α :=
  extend p q (List.ofFn (fun k : Fin n  seq p q k)).flatten

theorem seq_lemma_p (p q : List α  Prop) (n : ) : p (seq p q n) := by
  unfold seq
  apply extend_lemma_p

theorem seq_lemma_q (p q : List α  Prop) (n : ) :
    q (List.ofFn (fun k : Fin (n + 1)  seq p q k)).flatten := by
  rw [List.ofFn_succ_last]
  simp
  unfold seq
  apply extend_lemma_q p q

Jakub Nowak (Nov 14 2025 at 03:21):

Although, splitting is not necessary to avoid Classical.choose:

import Mathlib

variable {α : Type*}

noncomputable def extend (p q : List α  Prop) (u : List α) : { v : List α // p v  q (u ++ v) } := sorry

private noncomputable def seq (p q : List α  Prop) (n : ) : List α :=
  extend p q (List.ofFn (fun k : Fin n  seq p q k)).flatten

theorem seq_lemma (p q : List α  Prop) (n : ) :
    p (seq p q n)  q (List.ofFn (fun k : Fin (n + 1)  seq p q k)).flatten := by
  let r u v := p v  q (u ++ v)
  have : r (List.ofFn (fun k : Fin n  seq p q k)).flatten (seq p q n) := by
    rw [seq]
    exact (extend p q (List.ofFn (fun k : Fin n  seq p q k)).flatten).prop
  obtain h1, h2 := this
  refine h1, ?_⟩
  rw [List.ofFn_succ_last]
  simpa

Though, it's essentially the same, now you have to use Subtype.prop instead of Classical.choose_spec.

Ching-Tsun Chou (Nov 14 2025 at 23:07):

Thanks for the suggestions! After reading them and thinking more, I end up with the following code. The idea is to apply Classical.choose to extend_lemma first, so that Classical.choose_spec is very easy to use. Also, the reasoning about List and Fin is now concentrated in a single flatten_lemma, so that we are not distracted by them in the proof of seq_lemma'.

import Mathlib

variable {α : Type*}

theorem extend_lemma (p q : List α  Prop) (u : List α) :  v : List α, p v  q (u ++ v) := sorry

noncomputable def step (p q : List α  Prop) (u : List α) : List α :=
  Classical.choose <| extend_lemma p q u

theorem step_lemma (p q : List α  Prop) (u : List α) :
    p (step p q u)  q (u ++ step p q u) :=
  Classical.choose_spec <| extend_lemma p q u

lemma flatten_lemma {f :   List α} {n : } :
    (List.ofFn fun (k : Fin (n + 1))  f k).flatten =
    (List.ofFn fun (k : Fin n)  f k).flatten ++ f n := by
  rw [List.ofFn_succ_last]
  simp

noncomputable def seq' (p q : List α  Prop) (n : ) : List α :=
  step p q (List.ofFn (fun k : Fin n  seq' p q k)).flatten

theorem seq_lemma' (p q : List α  Prop) (n : ) :
    p (seq' p q n)  q (List.ofFn (fun k : Fin (n + 1)  seq' p q k)).flatten := by
  obtain h1, h2 := step_lemma p q (List.ofFn (fun k : Fin n  seq' p q k)).flatten
  constructor
  · rw [seq']
    exact h1
  · rw [flatten_lemma, seq']
    exact h2

Last updated: Dec 20 2025 at 21:32 UTC