1

I defined a cases rule for case_option in the hope of making some proofs more readable. However, when applying it with proof (cases rule: ...) and using the code snippet suggested by the proof statement, the Isar case syntax tells me Illegal schematic variable(s) in case ..., even though the rule works in other cases.

lemma case_option_cases[case_names None Some]: (* removing the "case_names" tag does not solve the issue *)
  assumes "x = None ==> P a"
    and "!!y. x = Some y ==> P (b y)"
  shows "P (case x of None => a | Some y => b y)"
  using assms unfolding option.split_sel by blast

notepad
begin

  fix P :: "'y => bool" and x :: "'x option" and a :: "'y" and b :: "'x => 'y"

  (* sanity check *)
  assume "x = None ==> P a" and "!!y. x = Some y ==> P (b y)"
  then have "P (case x of None => a | Some y => b y)"
    by (cases rule: case_option_cases) (* also works just "by (rule ...)" *)

  have "P (case x of None => a | Some y => b y)"
  proof (cases rule: case_option_cases) (* this line generates and suggests the following structure *)
    case None (* Illegal schematic variable(s) in case "None" *)
    then show ?thesis sorry
  next
    case (Some y) (* same here *)
    then show ?thesis sorry
  qed

end

Is there a way to fix this?

mini
  • 48
  • 5
  • 1
    A quick fix is to explicitly instantiate the required schematic variables of `case_option_cases`, i.e., use `proof (cases rule: case_option_cases[of x P a b])`. – Javier Díaz May 18 '22 at 00:17
  • 1
    I'd like to make an additional remark: I think the fundamental cause of the issues you face with your custom case analysis rule is that the `cases` method works smoothly with elimination rules (such as the built-in `option.exhaust` rule, which is the default rule for `cases` with `option`) since these do not change the goal. I think your `case_option_cases` lemma is closely related to `option.split`, which works well with the Simplifier (in fact, you proved `case_option_cases` by resorting to `option.split_sel`, although `using assms by (simp split: option.split)` works too and is simpler). – Javier Díaz May 19 '22 at 17:58
  • `option.exhaust` (and similarly, `.induct`) work well, but fail to eliminate the lengthy `case` expression, only half of which is relevant for each case. `option.split` is indeed very closely related to what I am trying to achieve. Is there any built-in way to apply the split as a proof starter? – mini May 20 '22 at 11:04
  • Please could you elaborate on the issue regarding the lengthy `case` expression? – Javier Díaz May 20 '22 at 12:42
  • Regarding the automatic application of split rules, you can add rules to the Splitter by using the attribute `split` when stating your lemma, i.e., `lemma "..." [split] ...`. – Javier Díaz May 21 '22 at 13:02
  • Regarding the lengthy `case` expressions: often I find ones of the form `complicated_predicate (case x of None => long_expr | Some y => longer_expr y)`. It quickly becomes tedious to work with these long expressions and proofs become hard to read, therefore I wanted a single-step tactic that gets rid of the `case` and instead exposes the simplified cases directly. Both methods in the answer achieve that. – mini May 25 '22 at 08:56
  • There is a built-in way to apply split rules: `split ` which is equivalent to `subst `, however. `proof (split option.split, safe)` generates the same subgoals as the methods in my answer, but does not generate cases. – mini Jun 01 '22 at 11:02

1 Answers1

0

As Javier points out, the solution is to instantiate the variables (only x seems to be required).
However, cases has this already built in:

proof (cases x rule: case_option_cases)
(* generates the following template *)
  case None
  then show ?thesis (* replace by "P a" *) sorry
next
  case (Some y)
  then show ?thesis (* replace by "P (b y)" *) sorry
qed

Note that the generated code still fails as ?thesis does not match the new goals after applying cases. Instead, the goals have to be stated explicitly.


Even better (although less intuitive), using induction in place of cases automatically instantiates the relevant variables, with the added bonus of providing the correct goals as ?case:

proof (induction rule: case_option_cases)
(* generates the following template *)
  case None
  then show ?case sorry
next
  case (Some y)
  then show ?case sorry
qed
mini
  • 48
  • 5
  • `proof (cases x rule: case_option_cases)` doesn't seem to work for me. – Javier Díaz May 18 '22 at 13:15
  • For me `cases x ...` solves the `Illegal schematic variable(s) ...` error, which is the issue in question. I am aware that the generated code still fails because `?thesis` no longer matches the goal (has to be stated explicitly: `then show "P a" ...`). I believe this is a limitation of `cases`, however. Could you describe what happens when you use `cases x ...`? – mini May 19 '22 at 09:34
  • 1
    Precisely, what happens is that `?thesis` no longer matches the goal and has to be stated explicitly. I think that should be part of the answer to the original question. – Javier Díaz May 19 '22 at 12:34
  • Thanks for editing your answer to include my suggestion. – Javier Díaz May 19 '22 at 15:44
  • 1
    I think it's worth pointing out that when explicitly instantiating *all* the schematic variables, as I mentioned in my very first comment, the correct goals are generated as `?case`'s. – Javier Díaz May 20 '22 at 02:15
  • This is some seriously non-intuitive behavior (although it would make sense that this is what `induction` does internally). Even weirder, `cases x P a ...` generates `?case` for `case None` and suggests it in the template, but not for `case (Some y)`. And `cases x P a b ...` fails with `Rule has fewer variables than instantiations given`. – mini May 20 '22 at 06:47