Zulip Chat Archive

Stream: general

Topic: contraposition


Patrick Massot (May 12 2019 at 12:23):

Still looking at my first year undergraduate course tactics, I see:

lemma contrapose (P Q : Prop) [decidable Q] : (¬ Q  ¬ P)  (P  Q) :=
λ h hP, by_contradiction (λ h', h h' hP)
open tactic interactive (parse loc) lean.parser (tk) interactive.loc

meta def tactic.interactive.contrapose (push : parse $ optional (tk "!" )) : tactic unit:=
do
  tgt  target,
  match tgt with
  | `(%%P  %%Q) := `[apply contrapose %%P %%Q]
  | _ := skip
  end,
  if push.is_some then try (tactic.interactive.push_neg (ns [none])) else skip

Of course we can already use rw not_imp_not or rw ← not_imp_not but you need to remember this name and the direction, and you don't have integration with push_neg. From a pedagogical point of view, I like to say in lectures: "reasoning by contrapositive" is an important technique, here is the Lean command to do that. In the same way we have by_contradiction and exfalso that are thin wrappers around the corresponding lemmas.

How people would feel if I PRed it into mathlib? I ask before writing the PR because the process involves efforts to put things at the right location, and write tests.

Rob Lewis (May 12 2019 at 14:08):

This sounds reasonable. A useful variant might be contrapose h. When the goal is Q with h : P is in the local context, this reverts h, does your thing, and then intros h : ¬ Q.

Patrick Massot (May 12 2019 at 14:10):

I guess adding this wouldn't cost much, and can be ignored by users who don't want it

Patrick Massot (May 12 2019 at 14:11):

I'll try that

Rob Lewis (May 12 2019 at 14:28):

By the way, are you sure the failure behavior is what you want? When the goal isn't an implies, your tactic either does nothing or reduces to push_neg. My instinct is that it should fail.

meta def tactic.interactive.contrapose (push : parse $ optional (tk "!" )) : tactic unit:=
do `(%%P  %%Q)  target,
   mk_app `contrapose [P, Q] >>= apply,
   when push.is_some $ try (tactic.interactive.push_neg (ns [none]))

Patrick Massot (May 12 2019 at 14:28):

You are right, it should fail. I'll modify this

Patrick Massot (May 12 2019 at 14:34):

Do we have a version of when where we get access to the assumption? (like when we do if h : ... then ... else ...)

Patrick Massot (May 12 2019 at 14:35):

Oh, I didn't see you wrote some code

Patrick Massot (May 12 2019 at 14:36):

I prefer the version I have now because it has a helpful error message

Patrick Massot (May 12 2019 at 14:38):

Is there any advantage of mk_app instead of my `[...] (besides the fact that it looks more profesionnal)?

Rob Lewis (May 12 2019 at 14:40):

Heh, I just noticed that when takes a decidable Prop. Figured it took a bool, but that's actually whenb.

Rob Lewis (May 12 2019 at 14:41):

Is there any advantage of mk_app instead of my `[...] (besides the fact that it looks more profesionnal)?

That, and it avoids entering interactive parsing mode. Not a big difference.

Patrick Massot (May 12 2019 at 14:42):

Hum, I also need to learn how to assert stuff is Prop. The pattern `(%%P → %%Q) is not specific enough

Rob Lewis (May 12 2019 at 14:44):

target >>= is_prop?

Patrick Massot (May 12 2019 at 14:47):

I wanted to test P and Q, but maybe testing target would be good enough

Patrick Massot (May 12 2019 at 14:48):

no, it's not good enough

Patrick Massot (May 12 2019 at 14:48):

It will be fooled by goals that look like forall x, P x

Patrick Massot (May 12 2019 at 14:49):

(and yes, students will try contrapose on such goals)

Patrick Massot (May 12 2019 at 14:51):

My wife asserts the goal's type is to mow the lawn. I'll read your explanations when I'll come back (there is also a tactic writing question in the choose thread, in case I'm too slow in the garden and you get bored).

Rob Lewis (May 12 2019 at 15:01):

Ah, yeah. Easiest to just check both with is_prop then.

Patrick Massot (May 12 2019 at 17:36):

Lean complains tactic 'infer_type' failed, given expression should not contain de-Bruijn variables, they should be replaced with local constants before using this tactic when I try to use is_prop on P or Q

Rob Lewis (May 12 2019 at 19:29):

That should only happen on Q when there's a dependency. Your tactic can fail if Q.has_var is true. Otherwise, is_prop should succeed on both P and Q, and if it's true in both cases you have a nondependent implication.

Rob Lewis (May 12 2019 at 19:30):

Alternatively, you could try to unify the goal with an application of implies, but I think doing those checks is more straightforward.

Patrick Massot (May 12 2019 at 19:32):

I was not catching failure at the right level

Patrick Massot (May 12 2019 at 19:34):

My current version is

meta def tactic.interactive.contrapose (push : parse (tk "!" )?) (hyp : parse ident?): tactic unit:=
do
  when hyp.is_some (do
    h  get_local (hyp.get_or_else `this),
    revert h,
    skip),
  tgt  target,
  let err := "The goal is not an implication, and you didn't specify an assumption",
  match tgt with
  | `(%%P  %%Q) := do (is_prop P >> is_prop Q) <|> fail err,
                       mk_app `contrapose [P, Q] >>= apply
  | _ := fail err
  end,
  when push.is_some $ try (tactic.interactive.push_neg (ns [none])),
  when hyp.is_some $ (intro $ hyp.get_or_else `this) >> skip

I don't like the get_or_else where I already now the option is some. But I don't know better

Rob Lewis (May 12 2019 at 19:57):

There's an error using mk_app, it needs mk_mapp. I think this is a bit cleaner:

meta def tactic.interactive.contrapose (push : parse (tk "!" )?) : parse ident?  tactic unit
| (some h) := get_local h >>= revert >> tactic.interactive.contrapose none >> intro h >> skip
| none :=
  do tgt  target,
  let err := "The goal is not an implication, and you didn't specify an assumption",
  match tgt with
  | `(%%P  %%Q) := do (is_prop P >> is_prop Q) <|> fail err,
                       mk_mapp `contrapose [P, Q] >>= apply
  | _ := fail err
  end,
  when push.is_some $ try (tactic.interactive.push_neg (loc.ns [none]))

Rob Lewis (May 12 2019 at 19:58):

The behavior of contrapose h will be funny if anything depends on h, but I think that's the user's problem.

Patrick Massot (May 12 2019 at 20:58):

https://github.com/leanprover-community/mathlib/pull/1015 Thanks a lot for all your help Rob!

Rob Lewis (May 12 2019 at 21:43):

No problem! I didn't notice before, but is_prop won't fail, it will just return true or false. So what you have now will give the error from mk_mapp if it gets a non-prop or dependent pi. If you only want to give the one error message, you should do something like this instead.

meta def tactic.interactive.contrapose (push : parse (tk "!" )?) : parse ident?  tactic unit
| (some h) := get_local h >>= revert >> tactic.interactive.contrapose none >> intro h >> skip
| none := let err := "The goal is not an implication, and you didn't specify an assumption" in
  (do `(%%P  %%Q)  target,
  mk_mapp `contrapose [P, Q] >>= apply,
  when push.is_some $ try (tactic.interactive.push_neg (loc.ns [none]))) <|> fail err

Last updated: Dec 20 2023 at 11:08 UTC