src/HOL/Tools/coinduction.ML
author haftmann
Fri Oct 10 19:55:32 2014 +0200 (2014-10-10)
changeset 58646 cd63a4b12a33
parent 58634 9f10d82e8188
child 58814 4c0ad4162cb7
permissions -rw-r--r--
specialized specification: avoid trivial instances
blanchet@54540
     1
(*  Title:      HOL/Tools/coinduction.ML
traytel@54026
     2
    Author:     Johannes Hölzl, TU Muenchen
traytel@54026
     3
    Author:     Dmitriy Traytel, TU Muenchen
traytel@54026
     4
    Copyright   2013
traytel@54026
     5
traytel@54026
     6
Coinduction method that avoids some boilerplate compared to coinduct.
traytel@54026
     7
*)
traytel@54026
     8
traytel@54026
     9
signature COINDUCTION =
traytel@54026
    10
sig
traytel@54026
    11
  val coinduction_tac: Proof.context -> term list -> thm option -> thm list -> cases_tactic
traytel@54026
    12
  val setup: theory -> theory
traytel@54026
    13
end;
traytel@54026
    14
traytel@54026
    15
structure Coinduction : COINDUCTION =
traytel@54026
    16
struct
traytel@54026
    17
blanchet@54540
    18
open Ctr_Sugar_Util
blanchet@54540
    19
open Ctr_Sugar_General_Tactics
traytel@54026
    20
traytel@54026
    21
fun filter_in_out _ [] = ([], [])
traytel@54026
    22
  | filter_in_out P (x :: xs) = (let
traytel@54026
    23
      val (ins, outs) = filter_in_out P xs;
traytel@54026
    24
    in
traytel@54026
    25
      if P x then (x :: ins, outs) else (ins, x :: outs)
traytel@54026
    26
    end);
traytel@54026
    27
traytel@54026
    28
fun ALLGOALS_SKIP skip tac st =
traytel@54026
    29
  let fun doall n = if n = skip then all_tac else tac n THEN doall (n - 1)
traytel@54026
    30
  in doall (nprems_of st) st  end;
traytel@54026
    31
traytel@54026
    32
fun THEN_ALL_NEW_SKIP skip tac1 tac2 i st =
traytel@54026
    33
  st |> (tac1 i THEN (fn st' =>
traytel@54026
    34
    Seq.INTERVAL tac2 (i + skip) (i + nprems_of st' - nprems_of st) st'));
traytel@54026
    35
traytel@54026
    36
fun DELETE_PREMS_AFTER skip tac i st =
traytel@54026
    37
  let
traytel@54026
    38
    val n = nth (prems_of st) (i - 1) |> Logic.strip_assums_hyp |> length;
traytel@54026
    39
  in
traytel@54026
    40
    (THEN_ALL_NEW_SKIP skip tac (REPEAT_DETERM_N n o etac thin_rl)) i st
traytel@54026
    41
  end;
traytel@54026
    42
wenzelm@56231
    43
fun coinduction_tac ctxt raw_vars opt_raw_thm prems = HEADGOAL (SUBGOAL_CASES (fn (goal, _, _) =>
traytel@54026
    44
  let
traytel@54026
    45
    val lhs_of_eq = HOLogic.dest_Trueprop #> HOLogic.dest_eq #> fst;
traytel@54026
    46
    fun find_coinduct t = 
traytel@54026
    47
      Induct.find_coinductP ctxt t @
traytel@54026
    48
      (try (Induct.find_coinductT ctxt o fastype_of o lhs_of_eq) t |> the_default [])
wenzelm@56231
    49
    val raw_thm =
wenzelm@56231
    50
      (case opt_raw_thm of
wenzelm@56231
    51
        SOME raw_thm => raw_thm
wenzelm@56231
    52
      | NONE => goal |> Logic.strip_assums_concl |> find_coinduct |> hd);
traytel@54026
    53
    val skip = Integer.max 1 (Rule_Cases.get_consumes raw_thm) - 1
traytel@54026
    54
    val cases = Rule_Cases.get raw_thm |> fst
traytel@54026
    55
  in
wenzelm@56231
    56
    HEADGOAL (
wenzelm@54742
    57
      Object_Logic.rulify_tac ctxt THEN'
traytel@54026
    58
      Method.insert_tac prems THEN'
wenzelm@54742
    59
      Object_Logic.atomize_prems_tac ctxt THEN'
traytel@54026
    60
      DELETE_PREMS_AFTER skip (Subgoal.FOCUS (fn {concl, context = ctxt, params, prems, ...} =>
traytel@54026
    61
        let
traytel@54026
    62
          val vars = raw_vars @ map (term_of o snd) params;
traytel@54026
    63
          val names_ctxt = ctxt
traytel@54026
    64
            |> fold Variable.declare_names vars
traytel@54026
    65
            |> fold Variable.declare_thm (raw_thm :: prems);
traytel@54026
    66
          val thm_concl = Thm.cprop_of raw_thm |> strip_imp_concl;
traytel@54026
    67
          val (rhoTs, rhots) = Thm.match (thm_concl, concl)
traytel@54026
    68
            |>> map (pairself typ_of)
traytel@54026
    69
            ||> map (pairself term_of);
traytel@54026
    70
          val xs = hd (Thm.prems_of raw_thm) |> HOLogic.dest_Trueprop |> strip_comb |> snd
traytel@54026
    71
            |> map (subst_atomic_types rhoTs);
traytel@54026
    72
          val raw_eqs = map (fn x => (x, AList.lookup op aconv rhots x |> the)) xs;
traytel@54026
    73
          val ((names, ctxt), Ts) = map_split (apfst fst o dest_Var o fst) raw_eqs
traytel@54026
    74
            |>> (fn names => Variable.variant_fixes names names_ctxt) ;
traytel@54026
    75
          val eqs =
wenzelm@58634
    76
            @{map 3} (fn name => fn T => fn (_, rhs) =>
traytel@54026
    77
              HOLogic.mk_eq (Free (name, T), rhs))
traytel@54026
    78
            names Ts raw_eqs;
Andreas@54366
    79
          val phi = eqs @ map (HOLogic.dest_Trueprop o prop_of) prems
traytel@54026
    80
            |> try (Library.foldr1 HOLogic.mk_conj)
traytel@54026
    81
            |> the_default @{term True}
traytel@54026
    82
            |> list_exists_free vars
traytel@54026
    83
            |> Term.map_abs_vars (Variable.revert_fixed ctxt)
traytel@54026
    84
            |> fold_rev Term.absfree (names ~~ Ts)
traytel@54026
    85
            |> certify ctxt;
traytel@54026
    86
          val thm = cterm_instantiate_pos [SOME phi] raw_thm;
traytel@54026
    87
          val e = length eqs;
traytel@54026
    88
          val p = length prems;
traytel@54026
    89
        in
traytel@54026
    90
          HEADGOAL (EVERY' [rtac thm,
traytel@54026
    91
            EVERY' (map (fn var =>
traytel@54026
    92
              rtac (cterm_instantiate_pos [NONE, SOME (certify ctxt var)] exI)) vars),
Andreas@54366
    93
            if p = 0 then CONJ_WRAP' (K (rtac refl)) eqs
Andreas@54366
    94
            else REPEAT_DETERM_N e o (rtac conjI THEN' rtac refl) THEN' CONJ_WRAP' rtac prems,
traytel@54026
    95
            K (ALLGOALS_SKIP skip
traytel@54026
    96
               (REPEAT_DETERM_N (length vars) o (etac exE THEN' rotate_tac ~1) THEN'
traytel@54026
    97
               DELETE_PREMS_AFTER 0 (Subgoal.FOCUS (fn {prems, params, context = ctxt, ...} =>
traytel@54026
    98
                 (case prems of
traytel@54026
    99
                   [] => all_tac
traytel@54026
   100
                 | inv::case_prems =>
traytel@54026
   101
                     let
traytel@54026
   102
                       val (init, last) = funpow_yield (p + e - 1) HOLogic.conj_elim inv;
traytel@54026
   103
                       val inv_thms = init @ [last];
Andreas@54366
   104
                       val eqs = take e inv_thms;
traytel@54026
   105
                       fun is_local_var t = 
traytel@54026
   106
                         member (fn (t, (_, t')) => t aconv (term_of t')) params t;
traytel@54026
   107
                        val (eqs, assms') = filter_in_out (is_local_var o lhs_of_eq o prop_of) eqs;
Andreas@54366
   108
                        val assms = assms' @ drop e inv_thms
traytel@54026
   109
                      in
traytel@54026
   110
                        HEADGOAL (Method.insert_tac (assms @ case_prems)) THEN
traytel@54026
   111
                        unfold_thms_tac ctxt eqs
traytel@54026
   112
                      end)) ctxt)))])
traytel@54026
   113
        end) ctxt) THEN'
wenzelm@56231
   114
      K (prune_params_tac ctxt))
wenzelm@56231
   115
    #> Seq.maps (fn st =>
traytel@54026
   116
      CASES (Rule_Cases.make_common (Proof_Context.theory_of ctxt, prop_of st) cases) all_tac st)
wenzelm@56231
   117
  end));
traytel@54026
   118
traytel@54026
   119
local
traytel@54026
   120
traytel@54026
   121
val ruleN = "rule"
traytel@54026
   122
val arbitraryN = "arbitrary"
traytel@54026
   123
fun single_rule [rule] = rule
traytel@54026
   124
  | single_rule _ = error "Single rule expected";
traytel@54026
   125
traytel@54026
   126
fun named_rule k arg get =
traytel@54026
   127
  Scan.lift (Args.$$$ k -- Args.colon) |-- Scan.repeat arg :|--
traytel@54026
   128
    (fn names => Scan.peek (fn context => Scan.succeed (names |> map (fn name =>
traytel@54026
   129
      (case get (Context.proof_of context) name of SOME x => x
traytel@54026
   130
      | NONE => error ("No rule for " ^ k ^ " " ^ quote name))))));
traytel@54026
   131
traytel@54026
   132
fun rule get_type get_pred =
wenzelm@55951
   133
  named_rule Induct.typeN (Args.type_name {proper = false, strict = false}) get_type ||
wenzelm@55954
   134
  named_rule Induct.predN (Args.const {proper = false, strict = false}) get_pred ||
wenzelm@55954
   135
  named_rule Induct.setN (Args.const {proper = false, strict = false}) get_pred ||
traytel@54026
   136
  Scan.lift (Args.$$$ ruleN -- Args.colon) |-- Attrib.thms;
traytel@54026
   137
traytel@54026
   138
val coinduct_rule = rule Induct.lookup_coinductT Induct.lookup_coinductP >> single_rule;
traytel@54026
   139
traytel@54026
   140
fun unless_more_args scan = Scan.unless (Scan.lift
traytel@54026
   141
  ((Args.$$$ arbitraryN || Args.$$$ Induct.typeN ||
traytel@54026
   142
    Args.$$$ Induct.predN || Args.$$$ Induct.setN || Args.$$$ ruleN) -- Args.colon)) scan;
traytel@54026
   143
traytel@54026
   144
val arbitrary = Scan.optional (Scan.lift (Args.$$$ arbitraryN -- Args.colon) |--
traytel@54026
   145
  Scan.repeat1 (unless_more_args Args.term)) [];
traytel@54026
   146
traytel@54026
   147
in
traytel@54026
   148
traytel@54026
   149
val setup =
traytel@54026
   150
  Method.setup @{binding coinduction}
traytel@54026
   151
    (arbitrary -- Scan.option coinduct_rule >>
wenzelm@58002
   152
      (fn (arbitrary, opt_rule) => fn ctxt => fn facts =>
wenzelm@58002
   153
        Seq.DETERM (coinduction_tac ctxt arbitrary opt_rule facts)))
traytel@54026
   154
    "coinduction on types or predicates/sets";
traytel@54026
   155
traytel@54026
   156
end;
traytel@54026
   157
traytel@54026
   158
end;
traytel@54026
   159