Zulip Chat Archive

Stream: new members

Topic: Force evaluation of List.tail


Kevin Cheung (Jun 13 2025 at 23:11):

For the following,

example :  x, x = [3,4] := by
  let g := [2,3,4]
  let h := g.tail
  simp [g] at h
  refine Exists.intro h ?_
  sorry -- simp [h, g] closes the goal

the infoview right before sorry shows

1 goal
g : List Nat := [2, 3, 4]
h : List Nat := g.tail
 h = [3, 4]

Is there a way to force evaluation of g.tail so that it shows h : List Nat := [3,4]?

Kenny Lau (Jun 13 2025 at 23:12):

@Kevin Cheung not exactly what you asked, but does this work?

example :  x, x = [3,4] := by
  let g := [2,3,4]
  let h := g.tail
  simp [g] at h
  refine Exists.intro h ?_
  have : h = [3,4] := rfl
  sorry -- simp [h, g] closes the goal

Kevin Cheung (Jun 13 2025 at 23:13):

I need something that doesn't mention [3,4] explicitly because I need to do something like this programmatically in a larger context.

Aaron Liu (Jun 13 2025 at 23:19):

Why do you want to force evaluation like this?

Kevin Cheung (Jun 13 2025 at 23:19):

Because I need to tail part and then take the head in a proof. If it doesn't reduce, the head does not return an element.

Aaron Liu (Jun 13 2025 at 23:20):

Sorry, I don't really understand.

Kenny Lau (Jun 13 2025 at 23:21):

@Kevin Cheung generalize does what you want (but the equality is flipped):

import Mathlib

example :  x, x = [3,4] := by
  generalize h₁ : [2,3,4] = l₁
  generalize h₂ : l₁.tail = l₂
  rw [ h₁, List.tail_cons] at h₂
  exact Exists.intro l₂ h₂.symm

Kenny Lau (Jun 13 2025 at 23:22):

it introduces a new variable with an equality as two separate lines in the infoview

Kenny Lau (Jun 13 2025 at 23:22):

but yeah this might be an XY problem (as Aaron is alluding to), you might find better solutions if you provide more context

Aaron Liu (Jun 13 2025 at 23:23):

Kevin Cheung said:

I need something that doesn't mention [3,4] explicitly because I need to do something like this programmatically in a larger context.

Well without any knowledge of the larger context I can't really help you.

Kevin Cheung (Jun 13 2025 at 23:24):

Here is the bigger context:

import Mathlib

def sumOfKPrimes (k n : ) :=  primes : List , primes.all Nat.Prime  primes.length = k  primes.sum = n

def foo (f : List ) :=
  match f with
  | List.nil => True
  | List.cons s f' => sumOfKPrimes 2 s  (foo f')

example : foo [13, 18, 20] := by
  dsimp [foo]
  let g := [[2,11], [5,13], [3,17]]
  apply And.intro
  . use g.head (by simp)
    simp [g]
    decide
  . let g' := List.tail g
    have : g'  [] := by aesop
    apply And.intro
    . use g'.head this
      simp [g', g]
      decide
    . let g'' := List.tail g'
      have : g''  [] := by aesop
      apply And.intro
      . use g''.head this
        simp [g'', g', g]
        decide
      . trivial

example : foo [13, 18, 20] := by
  simp [foo]
  constructorm* _  _
  . use [2, 11]; simp; decide
  . use [5, 13]; simp; decide
  . use [3, 17]; simp; decide

I need a way to handle a proof like this when the list has say 1000 items instead of 3. If there are better ways to do this, I would like to learn them.

Aaron Liu (Jun 13 2025 at 23:27):

So which part do you want to automate?

Kenny Lau (Jun 13 2025 at 23:27):

Kevin Cheung said:

I need a way to handle a proof like this when the list has say 1000 items instead of 3.

Forced evaluation wouldn't be a very good idea either, Lean is not a calculator :melt:

Kevin Cheung (Jun 13 2025 at 23:27):

Let's say I want to automate the use parts if I have a list of 1000 pairs that I know will work.

Aaron Liu (Jun 13 2025 at 23:28):

So you want to automatically find a pair that will work and insert it into the proof?

Aaron Liu (Jun 13 2025 at 23:28):

Or do you have the list of pairs on hand somewhere?

Kevin Cheung (Jun 13 2025 at 23:29):

Say the statement is foo xs where xs is a list of 1000 numbers. But I also already know ys a list of 1000 pairs of primes that for sure will work.

Aaron Liu (Jun 13 2025 at 23:29):

So you want to use ys as a witness to complete the proof?

Kenny Lau (Jun 13 2025 at 23:29):

@Kevin Cheung I would first write a function that can take the two lists and compute everything

Kevin Cheung (Jun 13 2025 at 23:29):

Basically, I want to construct a proof of foo xs from ys.

Kenny Lau (Jun 13 2025 at 23:29):

you don't want to copy that part of the code and paste 1000 times

Kenny Lau (Jun 13 2025 at 23:30):

(and please don't use a for loop to circumvent that)

Kevin Cheung (Jun 13 2025 at 23:30):

Kenny Lau said:

you don't want to copy that part of the code and paste 1000 times

I know I can just do this. But I hope that I don't need to.

Aaron Liu (Jun 13 2025 at 23:32):

This seems a lot like what is done with pi in file#Data/Real/Pi/Bounds

Yakov Pechersky (Jun 13 2025 at 23:34):

Can docs#List.foldr eliminate into Prop?

Yakov Pechersky (Jun 13 2025 at 23:35):

No :( because if it could, your foo is a fold over the list, and we have recursors for folds

Kenny Lau (Jun 13 2025 at 23:35):

@Kevin Cheung to demonstrate what I meant:

import Mathlib

def sumOfKPrimes (k n : ) :=  primes : List , primes.all Nat.Prime  primes.length = k  primes.sum = n

def foo (f : List ) :=
  match f with
  | List.nil => True
  | List.cons s f' => sumOfKPrimes 2 s  (foo f')

theorem foo_of (target : List ) (evidence : List ( × )) (h1 : target.length = evidence.length)
    (h2 :  i, (evidence.get i).1 + (evidence.get i).2 = target.get (Fin.cast h1.symm i)) : foo target :=
  sorry

example : foo [13, 18, 20] := by
  let g := [(2,11), (5,13), (3,17)]
  exact foo_of _ g rfl (by decide)

(there's probably a better way to do that)

Aaron Liu (Jun 13 2025 at 23:35):

Yakov Pechersky said:

Can docs#List.foldr eliminate into Prop?

Actually I see no reason why it couldn't

Yakov Pechersky (Jun 13 2025 at 23:36):

One could write an inline Tactic do that takes your list g and does a forM and calls the use tactic at each step

Kenny Lau (Jun 13 2025 at 23:45):

import Mathlib

def sumOfKPrimes (k n : ) :=  primes : List , primes.all Nat.Prime  primes.length = k  primes.sum = n

def foo (f : List ) :=
  match f with
  | List.nil => True
  | List.cons s f' => sumOfKPrimes 2 s  (foo f')

inductive PartialDecide (α : Prop) : Type
  | decided (prf : α) : PartialDecide α
  | undecided : PartialDecide α

def PartialDecide.result {α : Prop} : PartialDecide α  Prop
  | decided _ => α
  | undecided => True

theorem PartialDecide.prf {α : Prop} : (h : PartialDecide α)  h.result
  | decided prf => prf
  | undecided => trivial

def foo_of : (target : List )  (evidence : List ( × ))  PartialDecide (foo target)
  | [], [] => .decided trivial
  | [], (e :: et) => .undecided
  | (t :: tt), [] => .undecided
  | (t :: tt), (e :: et) => match foo_of tt et with
      | .decided ih => if h : e.1 + e.2 = t  e.1.Prime  e.2.Prime then
          .decided ⟨⟨[e.1, e.2], by simp [h], ih else .undecided
      | .undecided => .undecided

example : foo [13, 18, 20] :=
  (foo_of [13, 18, 20] [(2,11), (5,13), (3,17)]).prf

example : let tg := [13, 18, 20]; foo tg := by
  intro tg
  exact (foo_of tg [(2,11), (5,13), (3,17)]).prf

def List.repeat {α : Type*} (x : α) :   List α
  | 0 => []
  | (n+1) => x :: List.repeat x n

set_option trace.profiler true -- 3.89 sec
set_option maxRecDepth 100000 in
example : let tg := List.repeat 13 1000; foo tg := by
  intro tg
  exact (foo_of tg (List.repeat (2, 11) 1000)).prf

Kenny Lau (Jun 13 2025 at 23:45):

@Kevin Cheung this is the "better" code

Kenny Lau (Jun 13 2025 at 23:45):

it has a partial decision algorithm

Kenny Lau (Jun 13 2025 at 23:46):

comes at the cost that you would have to repeat the target twice... which could be circumvented if you make it a separate definition first
(edited: used let to avoid repeating it twice)

Kevin Cheung (Jun 13 2025 at 23:51):

Amazing. I can surely learn from these solutions. Thank you.

Kenny Lau (Jun 13 2025 at 23:51):

edit: stress tested the function for a list of length 1000, took 3.89 seconds


Last updated: Dec 20 2025 at 21:32 UTC