Zulip Chat Archive
Stream: lean4
Topic: Tactic backtracking
Paolo G. Giarrusso (Feb 03 2023 at 19:27):
Hi all! Lean4 newbie and Coq expert here. I'm wondering if Lean4 plans to support real logic-programming-style backtracking across tactics, to fix the 2nd/3rd example (like in Coq >= 8.4):
example (p q r : Prop) (hp : p) : (p ∨ q ∨ r) := by repeat constructor <;> assumption -- works
example (p q r : Prop) (hp : p) : (q ∨ p ∨ r) := by repeat constructor <;> assumption -- fails
example (p q r : Prop) (hp : p) : (q ∨ p ∨ r) := by
repeat (mplus | apply Or.inl | apply Or.inr) <;> assumption -- pseudocode
I'm aware of MonadBacktrack
and TC search, but I'm not sure either's an answer — and while in mathlib this might not matter, it seems useful enough in syntactic proofs that are useful in proof theory / programming languages / (metatheory of) program verification
Paolo G. Giarrusso (Feb 03 2023 at 19:34):
I also appreciate that sort-of backtracking is exponential, but that doesn't always make it "too" slow
Paolo G. Giarrusso (Feb 03 2023 at 19:37):
(what's "too slow" varies, but in practice I'm bothered by tactics taking more than >= 0.1s)
Mario Carneiro (Feb 03 2023 at 19:40):
mplus
is spelled first
btw
Paolo G. Giarrusso (Feb 03 2023 at 19:43):
not quite, first
isn't a sufficient mplus
:
example (p q r : Prop) (hp : p) : (q ∨ p ∨ r) := by
repeat (first | apply Or.inl | apply Or.inr) <;> assumption -- fails
Mario Carneiro (Feb 03 2023 at 19:43):
Can you describe in words how you want the search to proceed?
Mario Carneiro (Feb 03 2023 at 19:44):
it's not immediately obvious to me what semantics you want from that syntax
Mario Carneiro (Feb 03 2023 at 19:44):
One way to do a recursive backtracking search here is with a recursive macro:
syntax "stuff" : tactic
macro_rules
| `(tactic| stuff) => `(tactic| first | assumption | apply Or.inl; stuff | apply Or.inr; stuff)
example (p q r : Prop) (hp : p) : (q ∨ p ∨ r) := by stuff
Mario Carneiro (Feb 03 2023 at 19:44):
I think there is a way to do this with one of the repeat combinators but I haven't thought too hard about it
Mario Carneiro (Feb 03 2023 at 19:45):
you can also use a multiply-defined macro to similar effect:
syntax "stuff" : tactic
macro_rules | `(tactic| stuff) => `(tactic| assumption)
macro_rules | `(tactic| stuff) => `(tactic| (apply Or.inl; stuff))
macro_rules | `(tactic| stuff) => `(tactic| (apply Or.inr; stuff))
example (p q r : Prop) (hp : p) : (q ∨ p ∨ r) := by stuff
Mario Carneiro (Feb 03 2023 at 19:48):
There is certainly no difficulty in defining a combinator that has backtracking behavior, but <;>
and repeat
do not backtrack
Paolo G. Giarrusso (Feb 03 2023 at 19:48):
applying mplus (a; b) <;> c
on a goal would attempt a
, then attempt c
, and if that fails backtrack to try b
then c
.
Paolo G. Giarrusso (Feb 03 2023 at 19:50):
If there is no difficulty, can you define mplus
? Your examples encode the specific example, but I'm not sure they generalize
Paolo G. Giarrusso (Feb 03 2023 at 19:54):
I guess it's possible to define LogicTacticM
by stacking an actual logic monad with backtracking on top of TacticM
, lifting all tactics to LogicTacticM
, and adapting tactics to be multi-success where appropriate, but that seems invasive.
Mario Carneiro (Feb 03 2023 at 19:57):
This does what your description says, but it doesn't seem to be correct, since there is no recursion involved
syntax "mplus " withPosition((colGe "|" tacticSeq)+)
" => " tacticSeq : tactic
macro_rules
| `(tactic| mplus $[| $tac]* => $after) => do
let tac ← tac.mapM fun t => `(tactic| (($t); ($after)))
`(tactic| first $[| $tac:tactic]*)
example (p q r : Prop) (hp : p) : (q ∨ p ∨ r) := by
mplus | apply Or.inl | apply Or.inr => assumption
Paolo G. Giarrusso (Feb 03 2023 at 19:57):
while your workaround lets me define a new tactic for any fixed mplus (a; b) <;> c
, but I don't see how to write a first-class mplus (a; b)
— the correct denotational semantics supports returning multiple successes.
Mario Carneiro (Feb 03 2023 at 19:58):
Note that we have to take over the <;>
tactic here, since as I said that tactic doesn't backtrack and the way you have nested things makes it impossible for <;>
to do its work after mplus
is done
Mario Carneiro (Feb 03 2023 at 20:00):
You could have a special backtrackingTactic
syntax class and define all these combinators, but as you say that would be invasive. Do you have an example that can't be trivially rewritten so that the repetition can't be lifted to an explicit tactic combinator call?
Mario Carneiro (Feb 03 2023 at 20:03):
That is, this is easy if you allow putting mplus
as the root combinator and nesting <;> c
inside it, the problem is only with having mplus
passing multiple results and having everything after it get transparently run multiple times. This was an explicit design decision of the tactic language; we were aware of this behavior in Isabelle and Coq and chose not to replicate it because it leads to more difficult to understand control flow
Paolo G. Giarrusso (Feb 03 2023 at 20:06):
are you asking for evidence that (pure) Prolog doesn't trivially translate to functional programming? I'll admit I don't have a compelling one ready, mostly because I'd have to reverse-engineer old proofs
Mario Carneiro (Feb 03 2023 at 20:06):
I think the general version I posted above is equivalent in power to your mplus (a; b)
, you just have to pass the rest of the block (the scope of stuff that should be run multiple times) in the =>
, CPS style
Paolo G. Giarrusso (Feb 03 2023 at 20:07):
ah, that could work
Paolo G. Giarrusso (Feb 03 2023 at 20:09):
yeah, I buy that CPS is systematically sufficient. I'd have to reimplement needed tactics (here, constructor
and repeat
), but it should be doable as needed.
Mario Carneiro (Feb 03 2023 at 20:10):
do I understand correctly that constructor
nondeterministically picks a constructor to apply and repeat
nondeterministically decides how many times to iterate?
Mario Carneiro (Feb 03 2023 at 20:11):
I'm not sure exactly how repeat
interacts with the nondeterminism here
Paolo G. Giarrusso (Feb 03 2023 at 20:11):
I expect Coq's repeat
is greedy, but constructor
is multi-success
Mario Carneiro (Feb 03 2023 at 20:13):
but if you want to make a language out of this I think you could generalize the mplus
combinator above to
syntax "nondet" nondetTactic " => " tacticSeq : tactic
syntax "constructor" : nondetTactic
syntax "any" withPosition((colGe "|" tacticSeq)+) : nondetTactic
-- etc
Paolo G. Giarrusso (Feb 03 2023 at 20:15):
thank you; I'll need a bunch of reading to follow that, but this is good enough for now
Paolo G. Giarrusso (Feb 03 2023 at 20:29):
I guess the reading will need "Beyond Notations: Hygienic Macro Expansion for Theorem Proving Languages" and/or https://leanprover.github.io/lean4/doc/macro_overview.html.
I hope I won't need the videos in https://leanprover.github.io/lean4/doc/elaborators.html...
Sebastian Ullrich (Feb 03 2023 at 20:53):
Hey Paolo! You might also find the community-authored https://github.com/arthurpaulino/lean4-metaprogramming-book/ useful
Last updated: Dec 20 2023 at 11:08 UTC