Zulip Chat Archive
Stream: general
Topic: Multiple case
Sorawee Porncharoenwase (Jan 27 2021 at 09:39):
I have a custom tactic named muti_case
that I find myself using a lot. Today I just found out about https://github.com/leanprover-community/lean/commit/042d7e592f6e9356c3324beca1908419a423966e by @Eric Wieser which seems to be doing the same thing. I would love if this is a part of the core tactic. What's the status of this feature?
Moreover, I think it would be useful to allow *
as a case name too. It would for example allow us to write something like:
inductive val : Type
| a : val
| b : val
| c : val
| d : val
open val
def test : val → val → bool
| a c := tt
| a d := tt
| b a := ff
| b b := ff
| c a := tt
| c c := tt
| d d := ff
| _ _ := ff
example {x y} (h : test x y = tt) : x = a ∨ x = c :=
begin
cases x; cases y,
case [a *, c *] { simp },
case [b *, d *] { contradiction },
end
Eric Wieser (Jan 27 2021 at 09:41):
(leanprover-community/lean#508 for the PR)
Eric Wieser (Jan 27 2021 at 09:43):
There's some existing discussion about this in #metaprogramming / tactics
Mario Carneiro (Jan 27 2021 at 09:45):
the pr link is incorrect
Mario Carneiro (Jan 27 2021 at 09:46):
What's the actual tactic being proposed here?
Eric Wieser (Jan 27 2021 at 09:46):
Improvements to case
Mario Carneiro (Jan 27 2021 at 09:46):
I mean what does multi_case
do
Eric Wieser (Jan 27 2021 at 09:48):
Runs the tactic block on all matching cases
Eric Wieser (Jan 27 2021 at 09:50):
Fixed the link, and attempted to guess what the broken lines of the PR were meant to me - I got this working locally in a now discarded buffer, then must have failed at copying things piecewise into lean core
Sorawee Porncharoenwase (Jan 27 2021 at 09:51):
Here's my current definition of muti_case
:
@[interactive]
meta def multi_case
(ns : parse (list_of ident_*))
(ids : parse $ (tk ":" *> ident_*)?)
(tac : itactic) : tactic unit :=
ns.mmap' (λ ns, tactic.interactive.case ns ids tac)
Pretty much, it allows you to write:
multi_case [case1a case1b, case2a case2b] : v1 v2 {
tacs
}
Instead of:
case case1a case1b : v1 v2 {
tacs
},
case case2a case2b : v1 v2 {
tacs
},
The reason why I don't use any_goals
or any_goals
+ solve1
is that it often causes deterministic timeout for me.
The PR by @Eric Wieser is better because it doesn't force variable naming in each subcase to be the same.
Eric Wieser (Jan 27 2021 at 09:52):
Another difference is that in my version, within the {}
you can see all the matching goals, rather than just the first one
Sorawee Porncharoenwase (Jan 27 2021 at 09:52):
Ooh, that's very nice :)
Eric Wieser (Jan 27 2021 at 09:53):
So your
multi_case [case1a case1b, case2a case2b] : v1 v2 {
tacs
}
is the linked PR's
case [case1a case1b : v1 v2, case2a case2b: v1 v2] {
all_goals { tacs }
}
Mario Carneiro (Jan 27 2021 at 09:55):
If you can see all the matching goals, then does that mean there is an additional all_goals
wrapper before you can do things?
Mario Carneiro (Jan 27 2021 at 09:55):
oh you said this
Sorawee Porncharoenwase (Jan 27 2021 at 09:55):
Ah, I seem to misunderstand your PR then. I thought it's exactly the same as mine, but apparently that's not the case.
It's still a good improvement, IMO, and would be a better building block for my custom tactic.
Eric Wieser (Jan 27 2021 at 09:57):
The motivation for my design was:
- To make the goal view helpful
- To make something like this work:
case [case1a case1b : v1 v2, case2a case2b: v1 v2] {
all_goals { simp only [some_lemma] },
{ exact some_closing_lemma_1, }
{ exact some_closing_lemma_2, }
}
Mario Carneiro (Jan 27 2021 at 09:57):
I think that it makes sense to bind the variables differently for different cases, as in the PR, but the names should be "approximately the same" and the tactic script run in each case should be the same, suggesting that the all_goals
wrapper be built in
Eric Wieser (Jan 27 2021 at 09:58):
If the all goals wrapper is built in, there's no nice way to say "the proof for these two cases is almost but not quite identical".
Eric Wieser (Jan 27 2021 at 09:58):
I mean sure, you can use <|>
, but that obfuscates which goal you're actually trying to work on
Mario Carneiro (Jan 27 2021 at 09:58):
I think of this multi-case more like a match branch with an or pattern
Eric Wieser (Jan 27 2021 at 09:59):
I had a nasty time refactoring proofs a while back that used something like simp [foo] <|> simp [bar]
, as I removed a simp lemma the first branch was relying on, but all I got was errors about the second branch not matching
Eric Wieser (Jan 27 2021 at 10:00):
Mario Carneiro said:
I think of this multi-case more like a match branch with an or pattern
match branches with or are not a thing that exists today, right?
Mario Carneiro (Jan 27 2021 at 10:02):
They are in some languages, e,g:
enum Num { Neg(u32), Zero, Pos(u32) }
use Num::*;
fn abs(n: Num) -> u32 {
match n {
Neg(i) | Pos(i) => i,
Zero => 0,
}
}
Mario Carneiro (Jan 27 2021 at 10:04):
You can also kind of do or-patterns in lean using wildcard matches
Sorawee Porncharoenwase (Jan 27 2021 at 13:58):
Speaking of this, I have another tactic named soft_case
. It's exactly like the current case
, except that it is not wrapped with solve1
. This allows us to dive into a case, cases
on a variable in that case, go back up, and then all_goals
at the parent level. E.g.,
inductive val : Type
| a (x : bool) : val
| b : val
| c : val
| d : val
open val
def test : val → bool
| (a ff) := tt
| (a tt) := ff
| b := ff
| c := tt
| d := ff
example {x} (h : test x = tt) : x = (a ff) ∨ x = c :=
begin
cases x,
soft_case a : x { cases x },
all_goals { finish },
end
Would this be something useful to people?
Eric Wieser (Jan 27 2021 at 14:02):
focus_case
is probably a better name, for consistency with tactic#focus
Eric Wieser (Jan 27 2021 at 14:06):
The question is, what semantics would focus_case
with multiple cases have?
-- goals: A, B, C, D
focus_case [A, C] {
work_on_goal 0 {split},
work_on_goal 1 {split},
-- goals: A1, A2, C1, C2
}
-- what should the goal list be here?
Ideally you'd reconstruct A1, A2, B, C1, C2, D
, but I think the information to recover that simply doesn't exist
Mario Carneiro (Jan 27 2021 at 14:13):
I was also going to suggest something like soft_case
to address @Eric Wieser 's comment about cases that are almost but not quite the same. I think it could be implemented with an option on case
Mario Carneiro (Jan 27 2021 at 14:16):
I also have a slight preference for focus_case
being an option on case
instead of a separate tactic, although I don't have a good suggestion for what sigil is appropriate. case...
?
Eric Wieser (Jan 27 2021 at 14:17):
Also, what would be the semantics of:
focus_case succ : n {
let m := n,
},
focus_case succ : o {
-- wait, we already intro'd the variable as `n`?
},
Eric Wieser (Jan 27 2021 at 14:18):
Renaming all the n
s to o
s would be fine, but it's not obvious to me that that would just work out of the box
Mario Carneiro (Jan 27 2021 at 14:18):
If focus_case
works as I suggested, where the focus
part is built in, then in your example
-- goals: A, B, C, D
focus_case [A, C] {
split,
-- goals: A -> A1, A2, also C -> C1, C2
}
-- goals: A1, A2, B, C1, C2, D
is doable
Eric Wieser (Jan 27 2021 at 14:18):
How does focus_case know where to reinsert the new goals into the goal list?
Mario Carneiro (Jan 27 2021 at 14:18):
it is the one that deconstructed the list
Eric Wieser (Jan 27 2021 at 14:19):
That would require some way to inspect the A1 goal and ask "did you come from A or C", right?
Mario Carneiro (Jan 27 2021 at 14:19):
No, it is essentially list.bind
Mario Carneiro (Jan 27 2021 at 14:19):
where goals that aren't being focused are just inserted as is into the new goal list
Eric Wieser (Jan 27 2021 at 14:19):
Oh, I see your point - you're ok because your proposed focus_case
executes the tactic block on each goal
Eric Wieser (Jan 27 2021 at 14:20):
As opposed to executing it once against all goals
Mario Carneiro (Jan 27 2021 at 14:20):
right, that way of working is annoying anyway; most tactics want to work on a single goal
Mario Carneiro (Jan 27 2021 at 14:21):
especially with focus_case
you can just chain several of them if you want to do different things in each case
Mario Carneiro (Jan 27 2021 at 14:22):
The semantics of the double focus_case
is the same as case
: the variables get renamed on entry to the block
Mario Carneiro (Jan 27 2021 at 14:23):
-- a_1 : nat
focus_case succ : n {
-- n : nat
let m := n,
-- n : nat, m := n
},
focus_case succ : o {
-- o : nat, m := o
},
Eric Wieser (Jan 27 2021 at 14:24):
So, my current code is something like
case [A, B] {
all_goals { foo },
all_goals { bar },
work_on_goal 0 { bar0 },
work_on_goal 1 { bar1 },
all_goals { baz }, --done
}
I guess you're proposing I instead write that as
focus_case [A, B] { foo },
focus_case [A, B] { bar },
focus_case A { bar0 },
focus_case B { bar1 },
case [A, B] { baz },
Eric Wieser (Jan 27 2021 at 14:24):
The nice bit about my structure is that I can inspect the proof state without seeing goals C and D I don't care about
Eric Wieser (Jan 27 2021 at 14:24):
Whereas in your proposal, I either only see the proof state for A
, or I see it for all goals
Mario Carneiro (Jan 27 2021 at 14:25):
I think that should be fixed generally though
Eric Wieser (Jan 27 2021 at 14:25):
You'd consider that a flaw in the infoview?
Mario Carneiro (Jan 27 2021 at 14:26):
that's a server goal reporting issue
Mario Carneiro (Jan 27 2021 at 14:26):
if an interactive tactic block is called multiple times the goal view should show all of the incoming states
Eric Wieser (Jan 27 2021 at 14:27):
There's still a structuring argument that I'd like to wrap the A B part of the proof in braces to separate it from the C D part
Eric Wieser (Jan 27 2021 at 14:28):
Maybe there are other ways to achieve that
Mario Carneiro (Jan 27 2021 at 14:28):
maybe that needs its own tactic
Mario Carneiro (Jan 27 2021 at 14:28):
subcase
?
Eric Wieser (Jan 27 2021 at 14:29):
case_group
?
Mario Carneiro (Jan 27 2021 at 14:29):
cases
? :upside_down:
Eric Wieser (Jan 27 2021 at 14:31):
subcases
/ case_group
would have to be a solve
-style tactic though, unless you're ok with it inserting remaining goals in a random place
Mario Carneiro (Jan 27 2021 at 14:31):
probably it would put all the new goals at the front
Mario Carneiro (Jan 27 2021 at 14:32):
in particular, that means that case_group [B, C] {}
can be used to reorder A, B, C, D
to B, C, A, D
Eric Wieser (Jan 27 2021 at 14:35):
I thought about this a bit for work_on_goals
, where it was particularly nasty because if you want to work on goals 0
and 2
, then 1
and 3
, you have to use work_on_goals [0, 2] { }, work_on_goals [2, 3] { }
as after the first work_on_goals
the goals have re-ordered!
Eric Wieser (Jan 27 2021 at 14:35):
But I guess for named goals that problem goes away
Last updated: Dec 20 2023 at 11:08 UTC