Zulip Chat Archive
Stream: general
Topic: proof_by_contra
Johan Commelin (Oct 07 2021 at 13:14):
I would very much like to have the following tactic in mathlib. But presumably this should be merged into by_contra
, which is locked in core. And the behaviour of this tactic depends on push_neg
, which is only available in mathlib. What is the best way forward?
namespace tactic
namespace interactive
/-- Turns a goal `P` into `¬¬P`, and uses `push_neg` to simplify the inner `¬P`. -/
meta def proof_by_contra : tactic unit :=
`[ by_contra proof_by_contra_hyp,
try { push_neg at proof_by_contra_hyp },
revert proof_by_contra_hyp,
show ¬ _ ]
example (P : Prop) : P :=
begin
proof_by_contra, -- `¬¬P`
sorry
end
end interactive
end tactic
Gabriel Ebner (Oct 07 2021 at 13:16):
But presumably this should be merged into by_contra
It does something different than by_contra, so I don't think they should be merged.
Johan Commelin (Oct 07 2021 at 13:29):
@Gabriel Ebner I think this would be reasonable behaviour for by_contra
if it doesn't get a new id for the hypothesis.
Johan Commelin (Oct 07 2021 at 13:29):
Or maybe by_contra!
.
Gabriel Ebner (Oct 07 2021 at 13:29):
The push_neg is pretty mathlib-specific though.
Eric Wieser (Oct 07 2021 at 13:31):
Note that your tactic is a no-op for:
example (P : Prop) : ¬P :=
begin
proof_by_contra,
sorry
end
which is probably not what you want
Johan Commelin (Oct 07 2021 at 13:32):
I think it is what I want.
Eric Wieser (Oct 07 2021 at 13:32):
Aren't no-op tactics frowned upon?
Eric Wieser (Oct 07 2021 at 13:32):
eg simp
fails by default if it does nothing
Johan Commelin (Oct 07 2021 at 13:32):
You mean that it should fail instead?
Eric Wieser (Oct 07 2021 at 13:32):
Yes
Johan Commelin (Oct 07 2021 at 13:32):
Aha... I don't know how to do that.
Johan Commelin (Oct 07 2021 at 13:32):
I'm a cargo-tactic-writer
Eric Wieser (Oct 07 2021 at 13:33):
Probably you shouldn't inherit the intro
behavior from by_contra
Johan Commelin (Oct 07 2021 at 13:43):
I just want support for writing
have : p > n,
{ proof_by_contra,
assume : p ≤ n,
Johan Commelin (Oct 07 2021 at 13:44):
I don't care too much about what proof_by_contra
is called, as long as it is more readable than apply lt_of_not_ge
.
Johan Commelin (Oct 07 2021 at 13:45):
Every random stranger understands what proof_by_contra
does. But lt_of_not_ge
is hieroglyphic.
Kevin Buzzard (Oct 07 2021 at 13:45):
Just to comment that I'm extensively using by_contra
as it stands in a lecture I'm about to give and also in my undergraduate teaching this term. It's a great way of introducing classical logic to mathematicians without going on about constructivism
Johan Commelin (Oct 07 2021 at 13:45):
by_contra
is nice, except for the lack of automatic tidying up using push_neg
.
Kevin Buzzard (Oct 07 2021 at 13:47):
You don't always want to use push_neg
next though
Johan Commelin (Oct 07 2021 at 13:49):
Why not?
Johan Commelin (Oct 07 2021 at 13:49):
A proof that doesn't name any Prop-assumptions or library lemmas:
theorem euclid (n : ℕ) : ∃ p > n, p.prime :=
begin
let N := n! + 1,
let p := nat.min_fac N,
have : n! > 0 := by library_search,
have : N > 1 := by library_search,
have : N ≠ 1 := by library_search,
have : p.prime := by library_search,
have : p > n,
{ proof_by_contra,
assume : p ≤ n,
have : p > 0 := by library_search,
have : p ∣ n! := by library_search,
have : p ∣ N := by library_search,
have : p ∣ 1 := by library_search,
have : ¬p ∣ 1 := by library_search,
contradiction },
finish,
end
Johan Commelin (Oct 07 2021 at 13:51):
It's a bit long-winded. But this proof is readable by someone who has never read Lean before. I like that it doesn't contain any library lemmas.
Kevin Buzzard (Oct 07 2021 at 13:52):
That's pretty nice!
Yaël Dillies (Oct 07 2021 at 13:52):
Impressive!
Kevin Buzzard (Oct 07 2021 at 13:52):
Can I use it in my talk?
Yaël Dillies (Oct 07 2021 at 13:53):
But doesn't it set your CPU on fire?
Johan Commelin (Oct 07 2021 at 13:54):
Unfortunately you can't use
by library_search using [‹p ∣ n!›]
Eric Wieser (Oct 07 2021 at 13:54):
What imports does that example need?
Eric Wieser (Oct 07 2021 at 13:54):
The problem with library_search
is that it only works with the right imports, and without them you have no idea what you need to import!
Johan Commelin (Oct 07 2021 at 13:54):
Preamble:
import data.nat.prime
import tactic
namespace tactic
namespace interactive
/-- Turns a goal `P` into `¬¬P`, and uses `push_neg` to simplify the inner `¬P`. -/
meta def proof_by_contra : tactic unit :=
`[ by_contra proof_by_contra_hyp,
try { push_neg at proof_by_contra_hyp },
revert proof_by_contra_hyp,
show ¬ _ ]
example (P : Prop) : P :=
begin
proof_by_contra, -- `¬¬P`
sorry
end
end interactive
end tactic
open_locale nat -- enable `!` as notation for factorial
Mario Carneiro (Oct 07 2021 at 14:32):
Johan Commelin said:
I just want support for writing
have : p > n, { proof_by_contra, assume : p ≤ n,
I don't like that the proposed version of the tactic introduces a negation for no reason. If this is the kind of proof you want to do, why not make it proof_by_contra h? : p ≤ n
, i.e. the same style as by_contra
?
Johan Commelin (Oct 07 2021 at 14:34):
Mario, this is all due to the fact that I don't know how to properly write tactics.
Johan Commelin (Oct 07 2021 at 14:35):
So, if I can write
proof_by_contra,
assume : p ≤ n,
with a different implementation under the hood, then I certainly wont complain.
Johan Commelin (Oct 07 2021 at 14:38):
proof_by_contra : p ≤ n
reads slightly weird. It's missing the assume
verb.
Mario Carneiro (Oct 07 2021 at 14:40):
well then change the name of the tactic
Mario Carneiro (Oct 07 2021 at 14:40):
e.g. assume_by_contra
Eric Wieser (Oct 07 2021 at 14:41):
The existing by_contra
has no assume
in the name, though
Mario Carneiro (Oct 07 2021 at 14:42):
I think that it can also be merged into by_contra
in this form; the only addition here is that we are explicitly type ascribing the hypothesis
Mario Carneiro (Oct 07 2021 at 14:42):
and we use some simpa
call involving push neg stuff to make the type match
Eric Wieser (Oct 07 2021 at 14:43):
Am I right in thinking the main advantages of @Johan Commelin's spelling:
proof_by_contra,
assume : p ≤ n,
over
by_contra
push_neg at h
is that the former doesn't name the hypothesis and does show the type?
Johan Commelin (Oct 07 2021 at 14:44):
It also doesn't name push_neg
. Which is an operation that should be performed "behind the scenes" whenever possible.
Mario Carneiro (Oct 07 2021 at 14:44):
they both name the hypothesis
Mario Carneiro (Oct 07 2021 at 14:44):
the first one names it this
Johan Commelin (Oct 07 2021 at 14:44):
Eric means that the proof script doesn't name (i.e., mention explicitly) it.
Mario Carneiro (Oct 07 2021 at 14:45):
Doesn't by_contra
have a !
version that calls push_neg
?
Johan Commelin (Oct 07 2021 at 14:45):
by_contra
is in core, right?
Eric Wieser (Oct 07 2021 at 14:45):
I'd certainly be in favor of changing by_contra
to accept an optional type annotation
Eric Wieser (Oct 07 2021 at 14:45):
Yes, it is; I changed it very recently
Eric Wieser (Oct 07 2021 at 14:45):
tactic#by_contra to give you the breadcrumb to find it
Mario Carneiro (Oct 07 2021 at 14:46):
I could have sworn we had some souped up version of by_contra
in mathlib
Eric Wieser (Oct 07 2021 at 14:46):
Eric Wieser (Oct 07 2021 at 14:46):
https://github.com/leanprover-community/lean/blob/a0fb1e8c7ac81dfd2e80ad0de08f4e57ee853d82/library/init/meta/tactic.lean#L1481-L1487 is the tactic itself
Mario Carneiro (Oct 07 2021 at 14:47):
ah, there is tactic#contrapose
Johan Commelin (Oct 07 2021 at 14:47):
I would be in favour of having assume_by_contra
which also calls push_neg
on the new hyp.
Mario Carneiro (Oct 07 2021 at 14:47):
I think it would make sense to have a push_neg
powered by_contra
Mario Carneiro (Oct 07 2021 at 14:48):
would it be too tricky to remove by_contra
and just have by_contradiction
in core?
Johan Commelin (Oct 07 2021 at 14:48):
It might break projects that don't use mathlib?
Mario Carneiro (Oct 07 2021 at 14:48):
fair
Eric Wieser (Oct 07 2021 at 14:49):
I'm guessing it's not possible to have by_contra
in core and by_contra!
in mathlib?
Johan Commelin (Oct 07 2021 at 14:49):
Otoh, such project might not be using a recent Lean anyways.
Mario Carneiro (Oct 07 2021 at 14:49):
there is always the old standby of by_contra'
Johan Commelin (Oct 07 2021 at 14:49):
We never got complaints when we ripped algebra out of core.
Eric Wieser (Oct 07 2021 at 14:49):
Can we use replacement hooks like obviously
does, and have the default for by_contra!
to just print "sorry, needs mathlib"
Johan Commelin (Oct 07 2021 at 14:49):
Mario Carneiro said:
there is always the old standby of
by_contra'
which is ugly. My goal is to have a way to write really slick proofs.
Ruben Van de Velde (Oct 07 2021 at 14:50):
by_con
?
Johan Commelin (Oct 07 2021 at 14:50):
I don't think people will immediately understand that either.
Johan Commelin (Oct 07 2021 at 14:51):
The point is, when you write a proof that will be printed, there cannot be any mouse-over hover-texts, and there will be no goal state or infoview.
Mario Carneiro (Oct 07 2021 at 14:51):
I think for demo purposes you can set up your own veneer over mathlib tactics, so they aren't intrinsically related
Johan Commelin (Oct 07 2021 at 14:52):
That's fair. And that's what I'm doing now.
Mario Carneiro (Oct 07 2021 at 14:52):
like Patrick's "french mode"
Johan Commelin (Oct 07 2021 at 14:52):
Still, I would like to stay very close to mathlib.
Johan Commelin (Oct 07 2021 at 14:52):
Ideally, someone should be able to go to the online editor, and type in my proof.
Eric Wieser (Oct 07 2021 at 14:54):
Johan Commelin said:
Ideally, someone should be able to go to the online editor, and type in my proof.
I had complaints from a reviewer that attempts to copy my proof from a PDF version didn't work... So sharing a URL with .lean
files might be more reasonable.
Johan Commelin (Oct 07 2021 at 15:01):
That's a good idea.
Johan Commelin (Oct 07 2021 at 15:02):
Especially since ∣
≠ |
which will certainly trip up hand-copying of proofs.
Johan Commelin (Oct 07 2021 at 15:03):
In that case, can someone tell me how to write a observe : some_prop
tactic that does
have : some_prop := by library_search
Johan Commelin (Oct 07 2021 at 15:04):
I can then put proof_by_contra
and observe
in a prereq file that I import.
Mario Carneiro (Oct 07 2021 at 15:37):
import tactic.push_neg
import tactic.suggest
import data.nat.prime
namespace tactic
namespace interactive
setup_tactic_parser
meta def assume_by_contra (h : parse ident?) (t : parse (tk ":" *> texpr)?) : tactic unit := do
let h := h.get_or_else `this,
applyc ``classical.by_contradiction,
h₁ ← tactic.intro h,
t' ← infer_type h₁,
(e, pr) ← push_neg.normalize_negations t' <|> refl_conv t',
match t with
| none := () <$ replace_hyp h₁ e pr
| some t := do
t ← to_expr ``(%%t : Prop),
unify t e,
() <$ replace_hyp h₁ t pr
end
meta def observe (h : parse ident?) (t : parse (tk ":" *> texpr)) : tactic unit := do
let h := h.get_or_else `this,
t ← to_expr ``(%%t : Prop),
assert h t,
-- `[{library_search}]
solve1 $ do
(lemma_thunks, ctx_thunk) ← solve_by_elim.mk_assumption_set ff [] [],
(() <$ tactic.library_search
{ backtrack_all_goals := tt,
lemma_thunks := some lemma_thunks,
ctx_thunk := ctx_thunk,
md := tactic.transparency.reducible }) <|> fail "observe failed"
example (a b : ℕ) : a < b :=
begin
assume_by_contra : b ≤ a,
observe h : a ≤ a,
end
Mario Carneiro (Oct 07 2021 at 15:39):
it was harder than it should have been because the porcelain tactics forgot to expose the plumbing version, so I had to copy and paste bits of the implementation of push_neg
and library_search
respectively
Johan Commelin (Oct 07 2021 at 15:39):
I had done
meta def observe (h : parse ident?) (q₁ : parse (tk ":" *> texpr)?) : tactic unit :=
«have» h q₁ none >>
tactic.focus1 (tactic.library_search { backtrack_all_goals := tt } >>= (λ _, skip))
Mario Carneiro (Oct 07 2021 at 15:39):
library_search
should really have an option to turn off the trace in opt
Mario Carneiro (Oct 07 2021 at 15:40):
You probably don't want the type arg of observe
to be optional
Johan Commelin (Oct 07 2021 at 15:41):
I guess so :smiley:
Mario Carneiro (Oct 07 2021 at 15:41):
I'm not really sure what the mk_assumption_set
is doing, but library_search
was doing that so I copied it
Mario Carneiro (Oct 07 2021 at 15:43):
ah, you do want that stuff, it adds some default lemmas like rfl, trivial, congr_arg
to the simp set
Johan Commelin (Oct 07 2021 at 16:08):
Thanks for your help, Mario!
Patrick Massot (Oct 07 2021 at 16:10):
I'm a bit late to the party but I obviously support the idea of having a version of by_contra
that uses push_neg
(I wrote both push_neg
and contrapose
). I think we already had this conversation before and the fact that by_contra
is in core is the only obstruction (we can't simply add the exclamation mark version in mathlib)
Patrick Massot (Oct 07 2021 at 16:10):
I very frequently wish I could have this improved by_contra
even outside of propaganda talks
Mario Carneiro (Oct 07 2021 at 16:16):
I think that we should just do by_contra'
. We already have several tactics like that, they are basically TODOs for lean 4
Gabriel Ebner (Oct 07 2021 at 16:51):
(we can't simply add the exclamation mark version in mathlib)
To be pedantic, you can:
reserve notation `by_contra!`
meta def tactic.interactive.«by_contra!» : tactic unit :=
tactic.fail "todo"
example : true := by by_contra!
PRs are of course also welcome, but Lean 4 is on the horizon.
Kevin Buzzard (Oct 07 2021 at 21:40):
Just to add that "have p>n, by contra, assume p<=n" is pretty much exactly what a mathematician would write on a blackboard -- that's what Johan's idea has going for it
Kevin Buzzard (Oct 07 2021 at 21:44):
I really like observe
too
Yury G. Kudryashov (Oct 08 2021 at 03:14):
I would love to have a by_cases!
too, or even something that does
have := em p,
push_neg at this,
rcases this with (...|...),
Yury G. Kudryashov (Oct 08 2021 at 03:15):
This is useful, e.g., when I want to say: either ∀ x, f x ≤ g x
, or ∃ x, g x < f x
.
Mario Carneiro (Oct 08 2021 at 03:30):
Never See a Negation Again (TM)
Kevin Buzzard (Oct 08 2021 at 06:21):
Classical mathematicians don't take them seriously :-)
Yaël Dillies (Oct 08 2021 at 06:33):
I personally became quite fluent in using has_le.le.eq_or_lt
, ne.lt_or_lt
, lt_trichotomy
to optimize my case disjunctions, and I've been shortening quite a few existing proofs that doing by_cases
then rewriting to what they actually wanted to end up with.
Johan Commelin (Oct 08 2021 at 08:05):
@Mario Carneiro do you want to PR your assume_by_contra
as by_contra'
? Or would you mind if I do that?
Mario Carneiro (Oct 08 2021 at 08:05):
go ahead
Johan Commelin (Oct 08 2021 at 08:13):
I feel like the following doesn't really belong in tactic.norm_num
. Which file should it move to?
/-- Reflexivity conversion: given `e` returns `(e, ⊢ e = e)` -/
meta def refl_conv (e : expr) : tactic (expr × expr) :=
do p ← mk_eq_refl e, return (e, p)
/-- Turns a conversion tactic into one that always succeeds, where failure is interpreted as a
proof by reflexivity. -/
meta def or_refl_conv (tac : expr → tactic (expr × expr))
(e : expr) : tactic (expr × expr) := tac e <|> refl_conv e
/-- Transitivity conversion: given two conversions (which take an
expression `e` and returns `(e', ⊢ e = e')`), produces another
conversion that combines them with transitivity, treating failures
as reflexivity conversions. -/
meta def trans_conv (t₁ t₂ : expr → tactic (expr × expr)) (e : expr) :
tactic (expr × expr) :=
(do (e₁, p₁) ← t₁ e,
(do (e₂, p₂) ← t₂ e₁,
p ← mk_eq_trans p₁ p₂, return (e₂, p)) <|>
return (e₁, p₁)) <|> t₂ e
Anne Baanen (Oct 08 2021 at 08:14):
If nowhere else fits, I guess tactic/basic.lean
?
Johan Commelin (Oct 08 2021 at 08:21):
That's just a long list of imports.
Johan Commelin (Oct 08 2021 at 08:22):
@Mario Carneiro Can we soup up by_contra' h : t
, so that t
is equal to \not goal
modulo push_neg
?
Johan Commelin (Oct 08 2021 at 08:23):
In other words, t
doesn't have to be equal to the result of push_neg(\not goal)
. But push_neg(t)
should be.
Johan Commelin (Oct 08 2021 at 08:55):
@Mario Carneiro Does this look right?
meta def by_contra' (h : parse ident?) (t : parse (tk ":" *> texpr)?) : tactic unit := do
let h := h.get_or_else `this,
applyc ``classical.by_contradiction,
h₁ ← tactic.intro h,
t' ← infer_type h₁,
(e', pr') ← push_neg.normalize_negations t' <|> refl_conv t',
match t with
| none := () <$ replace_hyp h₁ e' pr'
| some t := do
t ← to_expr ``(%%t : Prop),
(e, pr) ← push_neg.normalize_negations t <|> refl_conv t,
unify e e',
() <$ replace_hyp h₁ e `(@eq.trans Prop %%t' %%e %%t %%pr' (eq.symm %%pr))
end
Mario Carneiro (Oct 08 2021 at 08:56):
you can use mk_eq_trans
toward the end there
Mario Carneiro (Oct 08 2021 at 08:57):
see trans_conv
above for inspiration
Johan Commelin (Oct 08 2021 at 09:01):
Like so?
p_aux ← mk_eq_symm pr,
p ← mk_eq_trans pr' p_aux,
() <$ replace_hyp h₁ e p
Mario Carneiro (Oct 08 2021 at 09:02):
the cool kids write mk_eq_symm pr >>= mk_eq_trans pr' >>= replace_hyp h₁ e
:D
Johan Commelin (Oct 08 2021 at 09:04):
gotcha
Johan Commelin (Oct 08 2021 at 09:04):
@Mario Carneiro do you agree if I move those refl_conv
things to tactic/core.lean
?
Mario Carneiro (Oct 08 2021 at 09:04):
yes, go ahead. You are right that they are misplaced
Johan Commelin (Oct 08 2021 at 10:07):
Last updated: Dec 20 2023 at 11:08 UTC