Library ASCommon.CInduction


From Stdlib Require Import Program.Tactics Arith.

From stdpp Require Export fin_maps sets.

Require Import Options.
Require Import CBase.

This module exists because I got fed up by how the normal induction tactic did not work on custom induction principles. This new tactic is named "cinduction" and is based on the "CInduction" typeclass. The induction lemma used can be either found by typeclass resolution or by specifying it explicitly.
For an integer of type nat, "induction n" and "cinduction n" do the same thing up to calling intro a few times.
But one can also use the lt_wf_cind instance by calling cinduction n using lt_wf_cind, to do a strong induction.
One can register a custom induction principle for any type, including propositions using the typeclass. The typeclass does not impose any shape between the input value and the induction predicate. If multiple value are needed, you can thus just call cinduction on a tuple.
In order to name the generated hypotheses, one can use "with", for example: cinduction n with >| intros n IH.
There is currently no way to use intro patterns in the same way as the normal induction.

Class CInduction {A : Type} (a : A) (P : Prop) :=
  {
    induction_requirement : Prop;
    induction_lemma : induction_requirement P
  }.

Arguments induction_lemma {_} _ {_ _}.
Arguments induction_requirement {_} _ {_ _}.


Ltac instanciate_as_found e :=
  let x := fresh in
  let H := fresh in
  pose (x := e);
  assert (x = e) as H; [reflexivity |];
  rewrite <- H; rewriteH;
  clear H; clear x.

Ltac pattern_for H :=
  lazymatch (type of H) with
  | _ ?a ?b ?c
      try(instanciate_as_found a);
      try(instanciate_as_found b);
      try(instanciate_as_found c);
      pattern a, b, c;
      apply H
  | _ ?a ?b
      try(instanciate_as_found a);
      try(instanciate_as_found b);
      pattern a, b;
      apply H
  | _ ?a
      try(instanciate_as_found a);
      pattern a;
      apply H
  | _fail "Not an application"
  end.

Tactic Notation "cinduction" constr(e) "with" tactic(intr) :=
  let H := fresh "H" in
  eenough (induction_requirement e) as H;
  [ apply (induction_lemma e) in H |
    hnf; repeat split; intr];
  [ repeat (pattern_for H); fail "Couldn't apply induction" | ..];
  cbn in ×.

Tactic Notation "cinduction" constr(e) := cinduction e with intros.

Tactic Notation "cinduction" constr(e) "using" constr(i) "with" tactic(intr) :=
  let P := mk_evar Prop in
  let CI := fresh "CI" in
  let _ := match goal with _evar (CI:CInduction e P) end in
  only [CI] : rapply i;
  let H := fresh "H" in
  eenough (@induction_requirement _ e _ CI) as H;
  [ apply (induction_lemma e) in H |
    hnf; repeat split; intr];
  [ repeat (pattern_for H); fail "Couldn't apply induction" | ..];
  cbn in *;
  clear CI.

Tactic Notation "cinduction" constr(e) "using" constr(i) :=
  cinduction e using i with intros.


Program Global Instance nat_cind (n : nat) (P : nat Prop) : CInduction n (P n) :=
  {|
    induction_requirement := (P 0) ( n, P n P (S n))
  |}.
Next Obligation.
  intros. induction n; hauto.
Defined.

Program Definition lt_wf_cind (n : nat) (P : nat Prop) : CInduction n (P n) :=
  {|
    induction_requirement := ( n, ( m, m < n P m) P n)
  |}.
Next Obligation. apply lt_wf_ind. Qed.

Program Global Instance le_cind (n m: nat) (H : n m) (P : nat Prop) :
  CInduction H (P m) :=
  {|
    induction_requirement := P n ( m, n m P m P (S m))
  |}.
Next Obligation. intros. induction H; hauto. Defined.

Program Global Instance list_cind A (l: list A) (P : list A Prop) :
  CInduction l (P l) :=
  {|
    induction_requirement := P [] ( a t, P t P (a :: t))
  |}.
Next Obligation. intros. induction l; hauto. Defined.

Program Definition list_rev_cind A (l: list A) (P : list A Prop) :
  CInduction l (P l) :=
  {|
    induction_requirement := P [] ( a t, P t P (t ++ [a]))
  |}.
Next Obligation. intros. apply rev_ind; hauto. Qed.