Zulip Chat Archive

Stream: new members

Topic: Is finish considered bad?


Sam (Aug 06 2022 at 20:04):

I've noticed (by a quick github search) that use of finish seems very rare in mathlib. However, I'm often using in it my own lemmas when finish works and it's not obvious what else to do.

Is finish considered bad? Should it be avoided in mathlib?

What are some techniques for dealing with situations where finish closes the goal, but it's not clear how to proceed otherwise? I'm aware of show_term, but finish usually spits out something pretty horrendous from that!

Alex J. Best (Aug 06 2022 at 20:11):

Finish can be a bit slow, so while it's not necessarily banned it often gets golfed out to speed things up. I guess it's a bit situational, but if you are using it for logic goals the tactics like tactic#tauto (and variants) do less than finish so are faster when they do work. If you post some examples we can take a look at alternative proofs.

Sam (Aug 06 2022 at 20:14):

I always at least try hint when resorting to finish, I think that will reliably tell me whether something like tauto is a valid substitute, won't it? I already only use it as a last resort, although I expect more experienced people know better techniques.

Matt Diamond (Aug 06 2022 at 20:14):

maybe squeeze_simp could help?

Matt Diamond (Aug 06 2022 at 20:15):

that tells you what works if simp works, and I believe finish calls the simplifier

Sam (Aug 06 2022 at 20:16):

I'm already doing a lot of simping. This is situation where simp * at * even isn't getting anywhere. I'll try to find a nice example that doesn't rely on too many other defs.

Sam (Aug 06 2022 at 20:19):

Here's a simple case where I'm using a finish.

import data.list

universe u

def list.containsp
{α : Type u}
(l : list α)
(p : α  Prop) [decidable_pred p] :=
 (a : α), a  l  p a

@[simp]
lemma list.containsp_cons_of_neg
{α : Type u}
{a : α}
{l : list α}
{p : α  Prop} [decidable_pred p]
(q : ¬ p a)
(h: (a::l).containsp p) :
l.containsp p :=
begin
  simp only [list.containsp, list.mem_cons_iff] at h,
  simp only [list.containsp],
  finish,
end

Sam (Aug 06 2022 at 20:19):

Ignore the name of that lemma, I think that's bad/wrong.

Sam (Aug 06 2022 at 20:21):

Maybe in this case there's some smart way to handle existentials that I don't know? hint, simp, library_search and suggest all seem like dead-ends as far as I can tell.

Ruben Van de Velde (Aug 06 2022 at 20:25):

Well, that's finish being too clever by half, imo :)

This works:

@[simp]
lemma list.containsp_cons_of_neg
{α : Type u}
{a : α}
{l : list α}
{p : α  Prop} [decidable_pred p]
(q : ¬ p a)
(h: (a::l).containsp p) :
l.containsp p :=
begin
  simp only [list.containsp, list.mem_cons_iff] at h,
  simp only [list.containsp],
  obtain b, rfl|hb1, hb2 := h,
  { contradiction },
  { exact b, hb1, hb2 },
end

Sam (Aug 06 2022 at 20:27):

Ah, so this is just me being a newb then. I don't think I've encountered obtain yet! I have some reading to do.

Sam (Aug 06 2022 at 20:27):

So essentially I just need to learn lots more tactics?

Ruben Van de Velde (Aug 06 2022 at 20:28):

At least several, I'd say - obtain is a version of cases with a little more bells and whistles (like the rfl|hb1 bit)

Ruben Van de Velde (Aug 06 2022 at 20:30):

I think that's a reason I'm not a fan of finish; if you rely on it for the simple problems, you don't learn as much, and you suddenly have to start learning on the difficult problems :)

Sam (Aug 06 2022 at 20:31):

Is there a good list of which tactics to focus on learning? I've been using a lot of simp, intro, split, induction, rw, split_ifs, apply, exact, assumption, tauto, finish, and cases. But clearly there are some "obvious" ones that I've missed!

Sam (Aug 06 2022 at 20:32):

Tbh I've been bruteforcing my way through some of those, I don't fully understand them all yet.

Kyle Miller (Aug 06 2022 at 20:32):

finish is being phased out of mathlib since it's not being ported to Lean 4.

My understanding is that it uses some Z3-like algorithms (SMT solving), and it uses lemmas with the ematch attribute.

Kyle Miller (Aug 06 2022 at 20:33):

I'm under the impression that it doesn't use simp

Damiano Testa (Aug 06 2022 at 20:35):

In terms of tactics, I think that the most common that I use are refine and rcases, the occasional rw and, when I want to fast-forward, convert. (I mentioned tactics that you did not mention, the ones in your list are also common.)

Kyle Miller (Aug 06 2022 at 20:35):

tactic#tidy is nice as an auto-solver sometimes

Sam (Aug 06 2022 at 20:36):

Oh, I forgot about tidy! That actually works for the example I posted.

Sam (Aug 06 2022 at 20:37):

Ah, so no finish in mathlib going forward, good to know! I hadn't realised it was SMT like! That explains the slowness.

Sam (Aug 06 2022 at 20:38):

Thanks for that list @Damiano Testa, I'll make a note to learn those.

Kyle Miller (Aug 06 2022 at 20:38):

You can do tidy? to have it output the proof it came up with. Sometimes you can hand-edit a reasonable proof out of it.

Damiano Testa (Aug 06 2022 at 20:38):

Ah, Kyle's mention of tidy made me remember of ext, which is also useful!

Sam (Aug 06 2022 at 20:43):

In mathlib, would it be considered better to inline tidy?'s result in the code? For the example I posted, it gives this:

  cases h,
  cases h_h,
  cases h_h_left,
  work_on_goal 1 {
    induction h_h_left,
    fsplit,
    work_on_goal 2 {
      fsplit, work_on_goal 1
      { solve_by_elim },
      assumption
    }
  },
  fsplit,
  work_on_goal 2 {
    fsplit,
    work_on_goal 1
    { assumption },
    assumption
  },

Is it reasonable to just put that in the proof? Is it acceptable to leave it just as tidy?

Sam (Aug 06 2022 at 20:45):

Do these automatic tactics ever have regressions when mathlib updates? That seems like it would be a reason not to use them.

Damiano Testa (Aug 06 2022 at 20:46):

I have found that the relative proportion of tactics that I use changed quite a bit with time. Initially, I used tidy, simp, dsimp, apply a lot. Later, I started preferring refine to apply to get finer control, I did not try simp/tidy on everything, but just when they were likely to make progress, started replacing rw by carefully reformulating statements to avoid needing it (when possible).

Damiano Testa (Aug 06 2022 at 20:47):

The chain of cases can likely be replaced by the output of rcases? h: try it out!

Damiano Testa (Aug 06 2022 at 20:49):

I am making the wild guess that the output of tidy above can be compacted to 3, max 4 lines.

Damiano Testa (Aug 06 2022 at 20:50):

(I'm not at a computer now, so cannot actually take my own challenge!)

Sam (Aug 06 2022 at 20:52):

Well we know

  obtain b, rfl|hb1, hb2 := h,
  { contradiction },
  { exact b, hb1, hb2 },

is equivalent to that tidy because @Ruben Van de Velde already found it! So yes, 3 lines. I'll give the rcases route a go!

Damiano Testa (Aug 06 2022 at 20:53):

Ah, i did not realize it was the same example! :face_palm:

Kyle Miller (Aug 06 2022 at 21:09):

By the way, this is a proof of that too:

begin
  rw list.containsp at h ,
  tidy,
end

Sam (Aug 06 2022 at 21:18):

Hah. Every time I think I'm getting better at making my proofs more succinct, somebody comes along and shows her verbose I was still being! The versions I wrote today are already better than the ones I wrote yesterday, so progress is in the right direction at least.

Damiano Testa (Aug 06 2022 at 21:19):

There is always someone on Zulip who has a shorter proof.

Damiano Testa (Aug 06 2022 at 21:20):

You'd be surprised by how infinite descent plays out with Lean.

Damiano Testa (Aug 07 2022 at 01:55):

This is what I obtained by using tidy? and then golfing:

begin
  rw [list.containsp] at h ,  -- this can be replace by the two simps that you had
  rcases h with h, rfl | h1, h2⟩,  -- takes care of the chain of `cases` and some of the `induction`s
  exact (q h2).elim,  -- this is the contradiction
  exact h, h1, h2⟩,  -- this is the other `split` and then the `assumption`s
end

Sam (Aug 07 2022 at 05:52):

Nice, thanks! I can't remember how small I got it by golfing, but you definitely did better than I did!

I've left it as tidy for now. Of course by line count that's the smallest, and tidy seems relatively common in mathlib, so is there a good reason to prefer to inline it instead of leaving it as tiny?

Damiano Testa (Aug 07 2022 at 06:00):

In this specific case, tidy does not call "non-terminal simps", so replacing tidy by the output of tidy? would just be for performance issues. If I remember correctly, though, tidy tries simp and dsimp "in the middle" and non-terminal (d)simps should be avoided.

Stuart Presnell (Aug 07 2022 at 10:55):

I set myself the project of eliminating the use of finish from the main body of mathlib back in December. I found tidy? was a good substitute in a lot of cases, generating an inelegant proof that I could then edit into a reasonable form, which was a good exercise in learning how to golf a proof.

Ruben Van de Velde (Aug 07 2022 at 11:31):

This is probably too short to be helpful:

@[simp]
lemma list.containsp_cons_of_neg
{α : Type u}
{a : α}
{l : list α}
{p : α  Prop} [decidable_pred p]
(q : ¬ p a)
(h: (a::l).containsp p) :
l.containsp p :=
begin
  simp only [list.containsp, list.mem_cons_iff] at h,
  simp only [list.containsp],
  exact h.imp (λ b hb, hb.1.resolve_left (λ hb', q (hb'  hb.2)), hb.2⟩),
end

Last updated: Dec 20 2023 at 11:08 UTC