Zulip Chat Archive

Stream: new members

Topic: general introductory advice around tactics


JJ (Jul 31 2025 at 20:07):

Hello! I am working my way through Analysis I as a way to learn the theorem proving side of Lean and had a couple of general questions. These are not specific to Analysis I -- they're just questions I had (being new to tactics) so I figured I'd put them here.

  1. If I have a hypotheses ¬A and a goal A, how do I tell Lean this is false? (Encountering this using by_cases.)
  2. Is there a nicer way to make a new hypothesis than (for example) constructing x++ = y++ via have hxy : x++ = a := hx; symm at hy; rw [hy] at hxy (for hx : x++ = a, hy : y++ a)? Ideally, I'd like to be able to do something like have hxy : x++ = y++ := (rw [(symm hy)] at hx) (fictitious syntax) in a sorta functional manner, but I don't know if this is possible.
    have hxy : x++ = y++ := hx.trans hy.symm

  3. On a similar tactic, can I get abel or some other simple rewriting tactic to rewrite a goal for me overtly? i.e. I have hx : a + c = b + c + x, and I would like to say like. replace hx := c + a = c + (b + x) by abel.

  4. Is there a way to see the tactics simp applied to close a goal?
    simp?

  5. Is apply And.intro or constructor more idiomatic?

  6. How do I apply a theorem taking arguments to a goal? Specifically, I have ⊢ c + a = c + (b + x) as my goal, and I have a theorem add_left_cancel (a b c : Nat) (habc = a + b = a + c) : b = c. I would like to do apply add_left_cancel c a (b + x) <goal>, but this doesn't work.
    forgot about backwards reasoning! normal apply works normally

  7. I have a hypothesis h : d ≠ 0 and a theorem isPos_iff (n : Nat) : n.IsPos ↔ n ≠ 0. How do I apply isPos_iff to the hypothesis? I think it is that isPos_iff takes an argument that is messing me up. This might be similar to the previous question, but since this is an ↔ I think that I should be able to do it with rewrite.

Thanks for any help!

Kenny Lau (Jul 31 2025 at 20:11):

2:

import Mathlib

example : 37 = 51 := by
  transitivity 100
  · show 37 = 100
    sorry
  · show 100 = 51
    sorry

Aaron Liu (Jul 31 2025 at 20:12):

rw [← hy] and rw [hy.symm] are both things you can do

Kenny Lau (Jul 31 2025 at 20:13):

3:

import Mathlib

example {a b c x : } (hx : a + c = b + c + x) : 1 + (c + a) = c + (b + x) + 1 := by
  rw [show c + a = c + (b + x) by abel_nf at hx ⊢; exact hx]
  abel

Kenny Lau (Jul 31 2025 at 20:13):

4: simp?

Kenny Lau (Jul 31 2025 at 20:13):

5:

import Mathlib

example : 0 = 1  1 = 0 := by
  refine ⟨?_, ?_⟩
  · sorry
  · sorry

Aaron Liu (Jul 31 2025 at 20:14):

You can also have hxy : x++ = y++ := hx.trans hy.symm

Kenny Lau (Jul 31 2025 at 20:14):

1:

import Mathlib

example {A : Prop} (ha : A) (hna : ¬ A) : False := by
  exact absurd ha hna

Aaron Liu (Jul 31 2025 at 20:15):

For number 6, what you are doing doesn't seem logically valid

Kenny Lau (Jul 31 2025 at 20:15):

6: what you're asking is not mathematically sound.

Kenny Lau (Jul 31 2025 at 20:15):

but there's rw [add_left_inj] instead

Kenny Lau (Jul 31 2025 at 20:16):

7 : rw [\l isPos_iff] at h

Matthew Jasper (Jul 31 2025 at 20:22):

For 6 there's also congr

JJ (Jul 31 2025 at 20:26):

6: Oh right yes, if I'm applying a theorem to a goal I'm reasoning backwards...

Michael Rothgang (Jul 31 2025 at 20:57):

Another option for 1:

import Mathlib
example {A : Prop} (ha : A) (hna : ¬ A) : False := by
  apply hna ha

Michael Rothgang (Jul 31 2025 at 20:58):

This works because ¬ A is defined to be A → False

JJ (Jul 31 2025 at 21:08):

Okay, working through understanding these. I have run into some rw confusion, though. I have an ih : n + b = 0 → n = 0, an h : n++ + b = 0, and a goal ⊢ n++ + b = 0 (for (b n : Nat). I would like to somehow... apply ih to h and obtain n++ = 0.

But I can't apply ih. I think there's something I don't understand going on there: n : Nat and n++ : Nat so I don't see why I can't apply ih.

But there's also something else I don't understand going on: I can rw [ih] at h (giving h : 0++ + b = 0) and I can rw [← ih] at h (giving h : n++ + b = n). What is going on here? I thought rewrite only functioned on equalities or biconditionals? And why is it transforming my hypotheses to false statements?

Kyle Miller (Jul 31 2025 at 21:10):

Rewrite supports conditional rewrites. It interprets ih as n = 0, and it adds an extra n + b = 0 goal.

JJ (Jul 31 2025 at 21:11):

Hm! Okay, that's a little unexpected. I see the new case hind now though.

Aaron Liu (Jul 31 2025 at 21:21):

JJ said:

Okay, working through understanding these. I have run into some rw confusion, though. I have an ih : n + b = 0 → n = 0, an h : n++ + b = 0, and a goal ⊢ n++ + b = 0 (for (b n : Nat). I would like to somehow... apply ih to h and obtain n++ = 0.

have hnsucc := ih h

JJ (Jul 31 2025 at 21:23):

That doesn't work because ih expects n + b = 0, but h is n++ + b = 0.

Aaron Liu (Jul 31 2025 at 21:24):

ok so explain again why you think applying ih to h is something you can do here

JJ (Jul 31 2025 at 21:25):

Because ih expects something of the form (n : Nat) + (b : Nat) = 0 and h is of the form (n++ : Nat) + (b : Nat) = 0.

Aaron Liu (Jul 31 2025 at 21:26):

yes they don't match because n++ is not the same as n

JJ (Jul 31 2025 at 21:27):

Right. I'm trying to understand why this is.

Aaron Liu (Jul 31 2025 at 21:28):

You're trying to understand why n++ is not the same as n?

JJ (Jul 31 2025 at 21:29):

No, I'm trying to understand why ih cannot take in h.

Aaron Liu (Jul 31 2025 at 21:30):

ih cannot take in h because ih eats a proof of n + b = 0 and h is a proof of n++ + b = 0 and n + b = 0 is not the same as n++ + b = 0

JJ (Jul 31 2025 at 21:30):

Right, but n and b are arbitrary. So I don't get why n++ can't be considered n for the purpose of ih.

Aaron Liu (Jul 31 2025 at 21:31):

no they're not arbitrary

Aaron Liu (Jul 31 2025 at 21:31):

since you have intro'd them they are fixed now

JJ (Jul 31 2025 at 21:32):

Hm.

Aaron Liu (Jul 31 2025 at 21:32):

if it was arbitrary then ih would look like ∀ k l, k + l = 0 → k = 0

Aaron Liu (Jul 31 2025 at 21:32):

but no it looks like n + b = 0 → n = 0

Aaron Liu (Jul 31 2025 at 21:33):

you have fixed them here

JJ (Jul 31 2025 at 21:33):

Like my full tactic state is (b n : Nat); (ih : n + b = 0 → n = 0); (h : n++ + b = 0) and I'm trying to derive n++ = 0. I look at this, I think "hm -- ih tells me that given some arbitrary n and b, if n + b = 0 then n = 0." I look at this again, I think "hm -- h tells me that for some n++ I have n++ + b = 0". So then I think I can apply h to ih but I obviously can't and now I'm confused.

Aaron Liu (Jul 31 2025 at 21:34):

you are reverting your variables incorrectly

JJ (Jul 31 2025 at 21:34):

What does that mean?

Aaron Liu (Jul 31 2025 at 21:35):

I see this as

I have b and n which are Nat
I have ih which tells me that if I give it a proof of n + b = 0 then it will give me a proof of n = 0
I have h which tells me that n++ + b = 0
I need to prove that n++ = 0

Aaron Liu (Jul 31 2025 at 21:36):

note the lack of arbitrariness here

JJ (Jul 31 2025 at 21:36):

Mmm, I see.

JJ (Jul 31 2025 at 21:38):

Ah! Right okay cool. I think I just closed it with induction. Which makes sense now, I think I was implicitly failing to consider the n = 0 case?

JJ (Jul 31 2025 at 21:41):

Or rather I guess this was by cases? Hrm. When n = 0, h : 0++ + b = 0 is false.

JJ (Jul 31 2025 at 21:44):

When n > 0... grr, I'm not sure what I actually just did. Is there a nice by_cases tactic that I should be using (over induction) for introductory stuff? by_cases itself is annoying, because it only takes in p and has the two cases be p and ¬p, and so far I don't actually know how to transform ¬a=0 into a ≠ 0. (And into a > 0, for that matter.)

Aaron Liu (Jul 31 2025 at 21:45):

¬a=0 is defeq to a ≠ 0

Aaron Liu (Jul 31 2025 at 21:46):

that means when you unfold all the definitions you get the same thing

Aaron Liu (Jul 31 2025 at 21:46):

so they're usually interchangeable

Aaron Liu (Jul 31 2025 at 21:47):

making this the same as a > 0 is a theorem

JJ (Jul 31 2025 at 21:50):

Ah, I found an example of what I was having trouble with. Yeah, it was not knowing how to convert ¬a = 0 to a ≠ 0.

JJ (Jul 31 2025 at 21:51):

So I've got a h : ¬d = 0 and an isPos_iff : (n : Nat) : n.isPos ↔ n ≠ 0. I couldn't rewrite [← isPos_iff] at h. But if I put replace h : d ≠ 0 := sorry before, rw [← isPos_iff] at h works fine.

JJ (Jul 31 2025 at 21:51):

It looks now like I can replace that sorry with h itself, but is there a way to avoid replace altogether?

Aaron Liu (Jul 31 2025 at 22:01):

rewrite with docs#ne_eq

Matt Diamond (Jul 31 2025 at 22:56):

Did people misread #1 or did JJ miswrite it? It says "If I have a hypotheses ¬A and a goal A, how do I tell Lean this is false?"

If you have a hypothesis ¬A and your goal is A, it's likely that something has gone wrong

JJ (Jul 31 2025 at 22:57):

Ah, sorry. The context is that's within a by_cases.

Matt Diamond (Jul 31 2025 at 22:58):

the context doesn't really matter, I'm asking if the goal is A or if the goal is False and you have both A and ¬A

JJ (Jul 31 2025 at 22:59):

The goal is A. I basically want to use absurd. Let me see if I can find where I was having trouble doing so.

JJ (Jul 31 2025 at 23:46):

Sorry! I can't find the place I was running into that. I think I rewrote it to not use by_cases.

JJ (Jul 31 2025 at 23:48):

Though speaking of by_cases: I have a lot of revert x; apply induction (this is equivalent to Mathlib induction I think, but is a theorem in the first chapter of this Analysis I text) scattered throughout, including in some places where I only really need by_cases. But in a lot of those induction places simp works where it doesn't in by_cases. Is by_cases the idiomatic cases tactic to use, or is there something better?

Aaron Liu (Jul 31 2025 at 23:49):

For doing cases it depends on what I'm casing on

Aaron Liu (Jul 31 2025 at 23:50):

sometimes I use by_cases but if there's a more convenient form I will use obtain with the lemma

Aaron Liu (Jul 31 2025 at 23:50):

like for example docs#eq_or_ne, docs#eq_or_lt_of_le, docs#Set.eq_empty_or_nonempty

Matt Diamond (Jul 31 2025 at 23:52):

oh yeah, or docs#Option.eq_none_or_eq_some ... doing rcases or obtain with those can be useful if you need more than just a simple P ∨ ¬P

Aaron Liu (Jul 31 2025 at 23:52):

or sometimes I will use match if I have a way of solving it for all n > 8 for example, and the n ≤ 8 is a case bash

JJ (Aug 01 2025 at 00:40):

Nice thanks! It seems like match is pretty amenable to what I'd been using apply induction for.

Matt Diamond (Aug 01 2025 at 00:45):

if you're calling revert before induction you might want to read up on the generalizing option

JJ (Aug 01 2025 at 00:45):

wow yeah match n with | 0 => ... | m++ => ... is really nice

JJ (Aug 01 2025 at 00:46):

I think this'll go away next chapter! This is using an induction theorem on a definition of Nat that is not the standard one

JJ (Aug 01 2025 at 00:54):

Wow... I can just. leave off branches of the match that lead to contradictions. That's very cool

Aaron Liu (Aug 01 2025 at 00:57):

wait until you hear about recursively calling the theorem from inside itself

JJ (Aug 01 2025 at 03:01):

Is there a nice way to clean this proof up? Specifically, I'm wondering

  1. when my goal is 0 = 0 ∨ m = 0 how to tell Lean "look at the left side"?
    tauto

  2. if there's a way to combine the two matches into one (just using (n,m) with cases (0,0), (0,_), (_,0), (a++,b++) didn't give me that n or m are zero in the first three cases)
    yes! just n,m. no parentheses around any match pattern.

  3. I apply succ_ne at h to get h : False and then follow it by contradiction -- any way to do it shorter?

lemma Nat.mul_eq_zero (n m: Nat) : n * m = 0  n = 0  m = 0 := by
  apply Iff.intro
  . intro h
    match n with
    | 0 => simp? -- goal: ⊢ 0 = 0 ∨ m = 0
    | a++ =>
      match m with
      | 0 => simp?
      | b++ =>
        rw [succ_mul, add_succ] at h
        apply succ_ne at h -- after this, h : False
        contradiction
  . intro h
    rcases h with left | right
    . rw [left, zero_mul]
    . rw [right, mul_zero]

Niels Voss (Aug 01 2025 at 08:14):

For 2., I think you can do something like

match n, m with
| 0, 0 => sorry
| 0, b++ => sorry
| a++, 0 => sorry
| a++, b++ => sorry

JJ (Aug 01 2025 at 08:17):

Oh! Thanks. For some reason I thought you needed ( ). That works nicely.

JJ (Aug 01 2025 at 08:18):

lemma Nat.mul_eq_zero (n m: Nat) : n * m = 0  n = 0  m = 0 := by
  refine ⟨?_, ?_⟩
  . intro h
    match n, m with
    | 0, _ | _, 0 => tauto
    | a++, b++ =>
      rw [succ_mul, add_succ] at h
      apply succ_ne at h -- after this, h : False
      contradiction
  . intro h
    rcases h with left | right
    . rw [left, zero_mul]
    . rw [right, mul_zero]

JJ (Aug 01 2025 at 08:19):

cool :)

Niels Voss (Aug 01 2025 at 08:24):

As for your last question, remember that:

  • False.elim takes in an element of False and produces anything
  • exfalso turns the current goal into False
  • apply succ_ne at h basically does replace h := succ_ne h

So technically, you could replace apply succ_ne at h; contradiction with exact (succ_ne h).elim, although it's debatable whether this is actually "better"

Niels Voss (Aug 01 2025 at 08:27):

You could also do nomatch succ_ne h. This is because False is an inductive type with no constructor, so when you pattern match on it, you actually don't even have to provide any cases.

Matt Diamond (Aug 01 2025 at 17:18):

when my goal is 0 = 0 ∨ m = 0 how to tell Lean "look at the left side"?

you listed tauto as the answer (and that will work) but for the more general case of focusing on the left branch of P ∨ Q you can use left


Last updated: Dec 20 2025 at 21:32 UTC