author | wenzelm |
Sun, 02 Oct 2016 15:35:56 +0200 | |
changeset 63994 | 18cbe1b8d859 |
parent 63680 | 6e1e8b5abbfa |
child 69597 | ff784d5a5bfb |
permissions | -rw-r--r-- |
61656 | 1 |
(*:maxLineLen=78:*) |
2 |
||
29755 | 3 |
theory Tactic |
4 |
imports Base |
|
5 |
begin |
|
18537 | 6 |
|
58618 | 7 |
chapter \<open>Tactical reasoning\<close> |
18537 | 8 |
|
61854 | 9 |
text \<open> |
10 |
Tactical reasoning works by refining an initial claim in a backwards |
|
11 |
fashion, until a solved form is reached. A \<open>goal\<close> consists of several |
|
12 |
subgoals that need to be solved in order to achieve the main statement; zero |
|
13 |
subgoals means that the proof may be finished. A \<open>tactic\<close> is a refinement |
|
14 |
operation that maps a goal to a lazy sequence of potential successors. A |
|
15 |
\<open>tactical\<close> is a combinator for composing tactics. |
|
16 |
\<close> |
|
18537 | 17 |
|
18 |
||
58618 | 19 |
section \<open>Goals \label{sec:tactical-goals}\<close> |
18537 | 20 |
|
58618 | 21 |
text \<open> |
61854 | 22 |
Isabelle/Pure represents a goal as a theorem stating that the subgoals imply |
23 |
the main goal: \<open>A\<^sub>1 \<Longrightarrow> \<dots> \<Longrightarrow> A\<^sub>n \<Longrightarrow> C\<close>. The outermost goal structure is that of |
|
24 |
a Horn Clause: i.e.\ an iterated implication without any quantifiers\<^footnote>\<open>Recall |
|
25 |
that outermost \<open>\<And>x. \<phi>[x]\<close> is always represented via schematic variables in |
|
26 |
the body: \<open>\<phi>[?x]\<close>. These variables may get instantiated during the course of |
|
27 |
reasoning.\<close>. For \<open>n = 0\<close> a goal is called ``solved''. |
|
18537 | 28 |
|
61854 | 29 |
The structure of each subgoal \<open>A\<^sub>i\<close> is that of a general Hereditary Harrop |
30 |
Formula \<open>\<And>x\<^sub>1 \<dots> \<And>x\<^sub>k. H\<^sub>1 \<Longrightarrow> \<dots> \<Longrightarrow> H\<^sub>m \<Longrightarrow> B\<close>. Here \<open>x\<^sub>1, \<dots>, x\<^sub>k\<close> are goal |
|
31 |
parameters, i.e.\ arbitrary-but-fixed entities of certain types, and \<open>H\<^sub>1, |
|
32 |
\<dots>, H\<^sub>m\<close> are goal hypotheses, i.e.\ facts that may be assumed locally. |
|
33 |
Together, this forms the goal context of the conclusion \<open>B\<close> to be |
|
34 |
established. The goal hypotheses may be again arbitrary Hereditary Harrop |
|
35 |
Formulas, although the level of nesting rarely exceeds 1--2 in practice. |
|
18537 | 36 |
|
61854 | 37 |
The main conclusion \<open>C\<close> is internally marked as a protected proposition, |
38 |
which is represented explicitly by the notation \<open>#C\<close> here. This ensures that |
|
39 |
the decomposition into subgoals and main conclusion is well-defined for |
|
40 |
arbitrarily structured claims. |
|
18537 | 41 |
|
61416 | 42 |
\<^medskip> |
61854 | 43 |
Basic goal management is performed via the following Isabelle/Pure rules: |
18537 | 44 |
|
45 |
\[ |
|
61493 | 46 |
\infer[\<open>(init)\<close>]{\<open>C \<Longrightarrow> #C\<close>}{} \qquad |
47 |
\infer[\<open>(finish)\<close>]{\<open>C\<close>}{\<open>#C\<close>} |
|
18537 | 48 |
\] |
49 |
||
61416 | 50 |
\<^medskip> |
61854 | 51 |
The following low-level variants admit general reasoning with protected |
52 |
propositions: |
|
18537 | 53 |
|
54 |
\[ |
|
61493 | 55 |
\infer[\<open>(protect n)\<close>]{\<open>A\<^sub>1 \<Longrightarrow> \<dots> \<Longrightarrow> A\<^sub>n \<Longrightarrow> #C\<close>}{\<open>A\<^sub>1 \<Longrightarrow> \<dots> \<Longrightarrow> A\<^sub>n \<Longrightarrow> C\<close>} |
52456 | 56 |
\] |
57 |
\[ |
|
61493 | 58 |
\infer[\<open>(conclude)\<close>]{\<open>A \<Longrightarrow> \<dots> \<Longrightarrow> C\<close>}{\<open>A \<Longrightarrow> \<dots> \<Longrightarrow> #C\<close>} |
18537 | 59 |
\] |
58618 | 60 |
\<close> |
18537 | 61 |
|
58618 | 62 |
text %mlref \<open> |
18537 | 63 |
\begin{mldecls} |
64 |
@{index_ML Goal.init: "cterm -> thm"} \\ |
|
32201
3689b647356d
updated Variable.focus, SUBPROOF, Obtain.result, Goal.finish;
wenzelm
parents:
30272
diff
changeset
|
65 |
@{index_ML Goal.finish: "Proof.context -> thm -> thm"} \\ |
52456 | 66 |
@{index_ML Goal.protect: "int -> thm -> thm"} \\ |
18537 | 67 |
@{index_ML Goal.conclude: "thm -> thm"} \\ |
68 |
\end{mldecls} |
|
69 |
||
61854 | 70 |
\<^descr> @{ML "Goal.init"}~\<open>C\<close> initializes a tactical goal from the well-formed |
71 |
proposition \<open>C\<close>. |
|
18537 | 72 |
|
61854 | 73 |
\<^descr> @{ML "Goal.finish"}~\<open>ctxt thm\<close> checks whether theorem \<open>thm\<close> is a solved |
74 |
goal (no subgoals), and concludes the result by removing the goal |
|
75 |
protection. The context is only required for printing error messages. |
|
18537 | 76 |
|
61854 | 77 |
\<^descr> @{ML "Goal.protect"}~\<open>n thm\<close> protects the statement of theorem \<open>thm\<close>. The |
78 |
parameter \<open>n\<close> indicates the number of premises to be retained. |
|
18537 | 79 |
|
61854 | 80 |
\<^descr> @{ML "Goal.conclude"}~\<open>thm\<close> removes the goal protection, even if there are |
81 |
pending subgoals. |
|
58618 | 82 |
\<close> |
18537 | 83 |
|
84 |
||
58618 | 85 |
section \<open>Tactics\label{sec:tactics}\<close> |
18537 | 86 |
|
61854 | 87 |
text \<open> |
88 |
A \<open>tactic\<close> is a function \<open>goal \<rightarrow> goal\<^sup>*\<^sup>*\<close> that maps a given goal state |
|
89 |
(represented as a theorem, cf.\ \secref{sec:tactical-goals}) to a lazy |
|
90 |
sequence of potential successor states. The underlying sequence |
|
91 |
implementation is lazy both in head and tail, and is purely functional in |
|
92 |
\<^emph>\<open>not\<close> supporting memoing.\<^footnote>\<open>The lack of memoing and the strict nature of ML |
|
93 |
requires some care when working with low-level sequence operations, to avoid |
|
94 |
duplicate or premature evaluation of results. It also means that modified |
|
95 |
runtime behavior, such as timeout, is very hard to achieve for general |
|
96 |
tactics.\<close> |
|
18537 | 97 |
|
61854 | 98 |
An \<^emph>\<open>empty result sequence\<close> means that the tactic has failed: in a compound |
99 |
tactic expression other tactics might be tried instead, or the whole |
|
100 |
refinement step might fail outright, producing a toplevel error message in |
|
101 |
the end. When implementing tactics from scratch, one should take care to |
|
102 |
observe the basic protocol of mapping regular error conditions to an empty |
|
103 |
result; only serious faults should emerge as exceptions. |
|
28781 | 104 |
|
61854 | 105 |
By enumerating \<^emph>\<open>multiple results\<close>, a tactic can easily express the |
106 |
potential outcome of an internal search process. There are also combinators |
|
107 |
for building proof tools that involve search systematically, see also |
|
108 |
\secref{sec:tacticals}. |
|
28781 | 109 |
|
61416 | 110 |
\<^medskip> |
61854 | 111 |
As explained before, a goal state essentially consists of a list of subgoals |
112 |
that imply the main goal (conclusion). Tactics may operate on all subgoals |
|
113 |
or on a particularly specified subgoal, but must not change the main |
|
114 |
conclusion (apart from instantiating schematic goal variables). |
|
18537 | 115 |
|
61854 | 116 |
Tactics with explicit \<^emph>\<open>subgoal addressing\<close> are of the form \<open>int \<rightarrow> tactic\<close> |
117 |
and may be applied to a particular subgoal (counting from 1). If the subgoal |
|
118 |
number is out of range, the tactic should fail with an empty result |
|
119 |
sequence, but must not raise an exception! |
|
28781 | 120 |
|
61854 | 121 |
Operating on a particular subgoal means to replace it by an interval of zero |
122 |
or more subgoals in the same place; other subgoals must not be affected, |
|
123 |
apart from instantiating schematic variables ranging over the whole goal |
|
124 |
state. |
|
28781 | 125 |
|
61854 | 126 |
A common pattern of composing tactics with subgoal addressing is to try the |
127 |
first one, and then the second one only if the subgoal has not been solved |
|
128 |
yet. Special care is required here to avoid bumping into unrelated subgoals |
|
129 |
that happen to come after the original subgoal. Assuming that there is only |
|
130 |
a single initial subgoal is a very common error when implementing tactics! |
|
28782 | 131 |
|
61854 | 132 |
Tactics with internal subgoal addressing should expose the subgoal index as |
133 |
\<open>int\<close> argument in full generality; a hardwired subgoal 1 is not acceptable. |
|
28781 | 134 |
|
61416 | 135 |
\<^medskip> |
61854 | 136 |
The main well-formedness conditions for proper tactics are summarized as |
137 |
follows. |
|
28781 | 138 |
|
61854 | 139 |
\<^item> General tactic failure is indicated by an empty result, only serious |
140 |
faults may produce an exception. |
|
28781 | 141 |
|
61854 | 142 |
\<^item> The main conclusion must not be changed, apart from instantiating |
143 |
schematic variables. |
|
28781 | 144 |
|
61854 | 145 |
\<^item> A tactic operates either uniformly on all subgoals, or specifically on a |
146 |
selected subgoal (without bumping into unrelated subgoals). |
|
28781 | 147 |
|
61854 | 148 |
\<^item> Range errors in subgoal addressing produce an empty result. |
28782 | 149 |
|
61854 | 150 |
Some of these conditions are checked by higher-level goal infrastructure |
151 |
(\secref{sec:struct-goals}); others are not checked explicitly, and |
|
152 |
violating them merely results in ill-behaved tactics experienced by the user |
|
153 |
(e.g.\ tactics that insist in being applicable only to singleton goals, or |
|
154 |
prevent composition via standard tacticals such as @{ML REPEAT}). |
|
58618 | 155 |
\<close> |
28782 | 156 |
|
58618 | 157 |
text %mlref \<open> |
28782 | 158 |
\begin{mldecls} |
159 |
@{index_ML_type tactic: "thm -> thm Seq.seq"} \\ |
|
28783 | 160 |
@{index_ML no_tac: tactic} \\ |
161 |
@{index_ML all_tac: tactic} \\ |
|
56491 | 162 |
@{index_ML print_tac: "Proof.context -> string -> tactic"} \\[1ex] |
28783 | 163 |
@{index_ML PRIMITIVE: "(thm -> thm) -> tactic"} \\[1ex] |
28782 | 164 |
@{index_ML SUBGOAL: "(term * int -> tactic) -> int -> tactic"} \\ |
165 |
@{index_ML CSUBGOAL: "(cterm * int -> tactic) -> int -> tactic"} \\ |
|
52463 | 166 |
@{index_ML SELECT_GOAL: "tactic -> int -> tactic"} \\ |
167 |
@{index_ML PREFER_GOAL: "tactic -> int -> tactic"} \\ |
|
28782 | 168 |
\end{mldecls} |
169 |
||
61854 | 170 |
\<^descr> Type @{ML_type tactic} represents tactics. The well-formedness conditions |
63680 | 171 |
described above need to be observed. See also \<^file>\<open>~~/src/Pure/General/seq.ML\<close> |
172 |
for the underlying implementation of lazy sequences. |
|
28782 | 173 |
|
61854 | 174 |
\<^descr> Type @{ML_type "int -> tactic"} represents tactics with explicit subgoal |
175 |
addressing, with well-formedness conditions as described above. |
|
28782 | 176 |
|
61854 | 177 |
\<^descr> @{ML no_tac} is a tactic that always fails, returning the empty sequence. |
28783 | 178 |
|
61854 | 179 |
\<^descr> @{ML all_tac} is a tactic that always succeeds, returning a singleton |
180 |
sequence with unchanged goal state. |
|
28783 | 181 |
|
61854 | 182 |
\<^descr> @{ML print_tac}~\<open>ctxt message\<close> is like @{ML all_tac}, but prints a message |
183 |
together with the goal state on the tracing channel. |
|
28783 | 184 |
|
61854 | 185 |
\<^descr> @{ML PRIMITIVE}~\<open>rule\<close> turns a primitive inference rule into a tactic with |
186 |
unique result. Exception @{ML THM} is considered a regular tactic failure |
|
187 |
and produces an empty result; other exceptions are passed through. |
|
28782 | 188 |
|
61854 | 189 |
\<^descr> @{ML SUBGOAL}~\<open>(fn (subgoal, i) => tactic)\<close> is the most basic form to |
190 |
produce a tactic with subgoal addressing. The given abstraction over the |
|
191 |
subgoal term and subgoal number allows to peek at the relevant information |
|
192 |
of the full goal state. The subgoal range is checked as required above. |
|
28782 | 193 |
|
61854 | 194 |
\<^descr> @{ML CSUBGOAL} is similar to @{ML SUBGOAL}, but passes the subgoal as |
195 |
@{ML_type cterm} instead of raw @{ML_type term}. This avoids expensive |
|
196 |
re-certification in situations where the subgoal is used directly for |
|
197 |
primitive inferences. |
|
28782 | 198 |
|
61854 | 199 |
\<^descr> @{ML SELECT_GOAL}~\<open>tac i\<close> confines a tactic to the specified subgoal \<open>i\<close>. |
200 |
This rearranges subgoals and the main goal protection |
|
201 |
(\secref{sec:tactical-goals}), while retaining the syntactic context of the |
|
202 |
overall goal state (concerning schematic variables etc.). |
|
52463 | 203 |
|
61854 | 204 |
\<^descr> @{ML PREFER_GOAL}~\<open>tac i\<close> rearranges subgoals to put \<open>i\<close> in front. This is |
205 |
similar to @{ML SELECT_GOAL}, but without changing the main goal protection. |
|
58618 | 206 |
\<close> |
18537 | 207 |
|
208 |
||
58618 | 209 |
subsection \<open>Resolution and assumption tactics \label{sec:resolve-assume-tac}\<close> |
28783 | 210 |
|
61854 | 211 |
text \<open> |
212 |
\<^emph>\<open>Resolution\<close> is the most basic mechanism for refining a subgoal using a |
|
213 |
theorem as object-level rule. \<^emph>\<open>Elim-resolution\<close> is particularly suited for |
|
214 |
elimination rules: it resolves with a rule, proves its first premise by |
|
215 |
assumption, and finally deletes that assumption from any new subgoals. |
|
216 |
\<^emph>\<open>Destruct-resolution\<close> is like elim-resolution, but the given destruction |
|
217 |
rules are first turned into canonical elimination format. |
|
218 |
\<^emph>\<open>Forward-resolution\<close> is like destruct-resolution, but without deleting the |
|
219 |
selected assumption. The \<open>r/e/d/f\<close> naming convention is maintained for |
|
220 |
several different kinds of resolution rules and tactics. |
|
28783 | 221 |
|
61854 | 222 |
Assumption tactics close a subgoal by unifying some of its premises against |
223 |
its conclusion. |
|
28783 | 224 |
|
61416 | 225 |
\<^medskip> |
61854 | 226 |
All the tactics in this section operate on a subgoal designated by a |
227 |
positive integer. Other subgoals might be affected indirectly, due to |
|
228 |
instantiation of schematic variables. |
|
28783 | 229 |
|
61854 | 230 |
There are various sources of non-determinism, the tactic result sequence |
231 |
enumerates all possibilities of the following choices (if applicable): |
|
28783 | 232 |
|
61854 | 233 |
\<^enum> selecting one of the rules given as argument to the tactic; |
234 |
||
235 |
\<^enum> selecting a subgoal premise to eliminate, unifying it against the first |
|
236 |
premise of the rule; |
|
28783 | 237 |
|
61854 | 238 |
\<^enum> unifying the conclusion of the subgoal to the conclusion of the rule. |
28783 | 239 |
|
61854 | 240 |
Recall that higher-order unification may produce multiple results that are |
241 |
enumerated here. |
|
58618 | 242 |
\<close> |
28783 | 243 |
|
58618 | 244 |
text %mlref \<open> |
28783 | 245 |
\begin{mldecls} |
59498
50b60f501b05
proper context for resolve_tac, eresolve_tac, dresolve_tac, forward_tac etc.;
wenzelm
parents:
58963
diff
changeset
|
246 |
@{index_ML resolve_tac: "Proof.context -> thm list -> int -> tactic"} \\ |
50b60f501b05
proper context for resolve_tac, eresolve_tac, dresolve_tac, forward_tac etc.;
wenzelm
parents:
58963
diff
changeset
|
247 |
@{index_ML eresolve_tac: "Proof.context -> thm list -> int -> tactic"} \\ |
50b60f501b05
proper context for resolve_tac, eresolve_tac, dresolve_tac, forward_tac etc.;
wenzelm
parents:
58963
diff
changeset
|
248 |
@{index_ML dresolve_tac: "Proof.context -> thm list -> int -> tactic"} \\ |
50b60f501b05
proper context for resolve_tac, eresolve_tac, dresolve_tac, forward_tac etc.;
wenzelm
parents:
58963
diff
changeset
|
249 |
@{index_ML forward_tac: "Proof.context -> thm list -> int -> tactic"} \\ |
50b60f501b05
proper context for resolve_tac, eresolve_tac, dresolve_tac, forward_tac etc.;
wenzelm
parents:
58963
diff
changeset
|
250 |
@{index_ML biresolve_tac: "Proof.context -> (bool * thm) list -> int -> tactic"} \\[1ex] |
58963
26bf09b95dda
proper context for assume_tac (atac remains as fall-back without context);
wenzelm
parents:
58957
diff
changeset
|
251 |
@{index_ML assume_tac: "Proof.context -> int -> tactic"} \\ |
28783 | 252 |
@{index_ML eq_assume_tac: "int -> tactic"} \\[1ex] |
58957 | 253 |
@{index_ML match_tac: "Proof.context -> thm list -> int -> tactic"} \\ |
254 |
@{index_ML ematch_tac: "Proof.context -> thm list -> int -> tactic"} \\ |
|
255 |
@{index_ML dmatch_tac: "Proof.context -> thm list -> int -> tactic"} \\ |
|
256 |
@{index_ML bimatch_tac: "Proof.context -> (bool * thm) list -> int -> tactic"} \\ |
|
28783 | 257 |
\end{mldecls} |
258 |
||
61854 | 259 |
\<^descr> @{ML resolve_tac}~\<open>ctxt thms i\<close> refines the goal state using the given |
260 |
theorems, which should normally be introduction rules. The tactic resolves a |
|
261 |
rule's conclusion with subgoal \<open>i\<close>, replacing it by the corresponding |
|
262 |
versions of the rule's premises. |
|
28783 | 263 |
|
61854 | 264 |
\<^descr> @{ML eresolve_tac}~\<open>ctxt thms i\<close> performs elim-resolution with the given |
265 |
theorems, which are normally be elimination rules. |
|
46278 | 266 |
|
59498
50b60f501b05
proper context for resolve_tac, eresolve_tac, dresolve_tac, forward_tac etc.;
wenzelm
parents:
58963
diff
changeset
|
267 |
Note that @{ML_text "eresolve_tac ctxt [asm_rl]"} is equivalent to @{ML_text |
50b60f501b05
proper context for resolve_tac, eresolve_tac, dresolve_tac, forward_tac etc.;
wenzelm
parents:
58963
diff
changeset
|
268 |
"assume_tac ctxt"}, which facilitates mixing of assumption steps with |
46278 | 269 |
genuine eliminations. |
28783 | 270 |
|
61854 | 271 |
\<^descr> @{ML dresolve_tac}~\<open>ctxt thms i\<close> performs destruct-resolution with the |
272 |
given theorems, which should normally be destruction rules. This replaces an |
|
273 |
assumption by the result of applying one of the rules. |
|
28783 | 274 |
|
61854 | 275 |
\<^descr> @{ML forward_tac} is like @{ML dresolve_tac} except that the selected |
276 |
assumption is not deleted. It applies a rule to an assumption, adding the |
|
277 |
result as a new assumption. |
|
28783 | 278 |
|
61854 | 279 |
\<^descr> @{ML biresolve_tac}~\<open>ctxt brls i\<close> refines the proof state by resolution or |
280 |
elim-resolution on each rule, as indicated by its flag. It affects subgoal |
|
281 |
\<open>i\<close> of the proof state. |
|
50072 | 282 |
|
61854 | 283 |
For each pair \<open>(flag, rule)\<close>, it applies resolution if the flag is \<open>false\<close> |
284 |
and elim-resolution if the flag is \<open>true\<close>. A single tactic call handles a |
|
285 |
mixture of introduction and elimination rules, which is useful to organize |
|
286 |
the search process systematically in proof tools. |
|
50072 | 287 |
|
61854 | 288 |
\<^descr> @{ML assume_tac}~\<open>ctxt i\<close> attempts to solve subgoal \<open>i\<close> by assumption |
289 |
(modulo higher-order unification). |
|
28783 | 290 |
|
61854 | 291 |
\<^descr> @{ML eq_assume_tac} is similar to @{ML assume_tac}, but checks only for |
292 |
immediate \<open>\<alpha>\<close>-convertibility instead of using unification. It succeeds (with |
|
293 |
a unique next state) if one of the assumptions is equal to the subgoal's |
|
294 |
conclusion. Since it does not instantiate variables, it cannot make other |
|
295 |
subgoals unprovable. |
|
28783 | 296 |
|
61854 | 297 |
\<^descr> @{ML match_tac}, @{ML ematch_tac}, @{ML dmatch_tac}, and @{ML bimatch_tac} |
298 |
are similar to @{ML resolve_tac}, @{ML eresolve_tac}, @{ML dresolve_tac}, |
|
299 |
and @{ML biresolve_tac}, respectively, but do not instantiate schematic |
|
300 |
variables in the goal state.\<^footnote>\<open>Strictly speaking, matching means to treat the |
|
301 |
unknowns in the goal state as constants, but these tactics merely discard |
|
302 |
unifiers that would update the goal state. In rare situations (where the |
|
303 |
conclusion and goal state have flexible terms at the same position), the |
|
304 |
tactic will fail even though an acceptable unifier exists.\<close> These tactics |
|
305 |
were written for a specific application within the classical reasoner. |
|
28783 | 306 |
|
307 |
Flexible subgoals are not updated at will, but are left alone. |
|
58618 | 308 |
\<close> |
28783 | 309 |
|
310 |
||
58618 | 311 |
subsection \<open>Explicit instantiation within a subgoal context\<close> |
28785
64163cddf3e6
added section "Explicit instantiation within a subgoal context";
wenzelm
parents:
28783
diff
changeset
|
312 |
|
61854 | 313 |
text \<open> |
314 |
The main resolution tactics (\secref{sec:resolve-assume-tac}) use |
|
315 |
higher-order unification, which works well in many practical situations |
|
316 |
despite its daunting theoretical properties. Nonetheless, there are |
|
317 |
important problem classes where unguided higher-order unification is not so |
|
318 |
useful. This typically involves rules like universal elimination, |
|
319 |
existential introduction, or equational substitution. Here the unification |
|
320 |
problem involves fully flexible \<open>?P ?x\<close> schemes, which are hard to manage |
|
28785
64163cddf3e6
added section "Explicit instantiation within a subgoal context";
wenzelm
parents:
28783
diff
changeset
|
321 |
without further hints. |
64163cddf3e6
added section "Explicit instantiation within a subgoal context";
wenzelm
parents:
28783
diff
changeset
|
322 |
|
61854 | 323 |
By providing a (small) rigid term for \<open>?x\<close> explicitly, the remaining |
324 |
unification problem is to assign a (large) term to \<open>?P\<close>, according to the |
|
325 |
shape of the given subgoal. This is sufficiently well-behaved in most |
|
326 |
practical situations. |
|
28785
64163cddf3e6
added section "Explicit instantiation within a subgoal context";
wenzelm
parents:
28783
diff
changeset
|
327 |
|
61416 | 328 |
\<^medskip> |
61854 | 329 |
Isabelle provides separate versions of the standard \<open>r/e/d/f\<close> resolution |
330 |
tactics that allow to provide explicit instantiations of unknowns of the |
|
331 |
given rule, wrt.\ terms that refer to the implicit context of the selected |
|
332 |
subgoal. |
|
28785
64163cddf3e6
added section "Explicit instantiation within a subgoal context";
wenzelm
parents:
28783
diff
changeset
|
333 |
|
61854 | 334 |
An instantiation consists of a list of pairs of the form \<open>(?x, t)\<close>, where |
335 |
\<open>?x\<close> is a schematic variable occurring in the given rule, and \<open>t\<close> is a term |
|
336 |
from the current proof context, augmented by the local goal parameters of |
|
337 |
the selected subgoal; cf.\ the \<open>focus\<close> operation described in |
|
28785
64163cddf3e6
added section "Explicit instantiation within a subgoal context";
wenzelm
parents:
28783
diff
changeset
|
338 |
\secref{sec:variables}. |
64163cddf3e6
added section "Explicit instantiation within a subgoal context";
wenzelm
parents:
28783
diff
changeset
|
339 |
|
61854 | 340 |
Entering the syntactic context of a subgoal is a brittle operation, because |
341 |
its exact form is somewhat accidental, and the choice of bound variable |
|
342 |
names depends on the presence of other local and global names. Explicit |
|
343 |
renaming of subgoal parameters prior to explicit instantiation might help to |
|
344 |
achieve a bit more robustness. |
|
28785
64163cddf3e6
added section "Explicit instantiation within a subgoal context";
wenzelm
parents:
28783
diff
changeset
|
345 |
|
61854 | 346 |
Type instantiations may be given as well, via pairs like \<open>(?'a, \<tau>)\<close>. Type |
347 |
instantiations are distinguished from term instantiations by the syntactic |
|
348 |
form of the schematic variable. Types are instantiated before terms are. |
|
349 |
Since term instantiation already performs simple type-inference, so explicit |
|
350 |
type instantiations are seldom necessary. |
|
58618 | 351 |
\<close> |
28785
64163cddf3e6
added section "Explicit instantiation within a subgoal context";
wenzelm
parents:
28783
diff
changeset
|
352 |
|
58618 | 353 |
text %mlref \<open> |
28785
64163cddf3e6
added section "Explicit instantiation within a subgoal context";
wenzelm
parents:
28783
diff
changeset
|
354 |
\begin{mldecls} |
59763 | 355 |
@{index_ML Rule_Insts.res_inst_tac: "Proof.context -> |
59780 | 356 |
((indexname * Position.T) * string) list -> (binding * string option * mixfix) list -> |
357 |
thm -> int -> tactic"} \\ |
|
59763 | 358 |
@{index_ML Rule_Insts.eres_inst_tac: "Proof.context -> |
59780 | 359 |
((indexname * Position.T) * string) list -> (binding * string option * mixfix) list -> |
360 |
thm -> int -> tactic"} \\ |
|
59763 | 361 |
@{index_ML Rule_Insts.dres_inst_tac: "Proof.context -> |
59780 | 362 |
((indexname * Position.T) * string) list -> (binding * string option * mixfix) list -> |
363 |
thm -> int -> tactic"} \\ |
|
59763 | 364 |
@{index_ML Rule_Insts.forw_inst_tac: "Proof.context -> |
59780 | 365 |
((indexname * Position.T) * string) list -> (binding * string option * mixfix) list -> |
366 |
thm -> int -> tactic"} \\ |
|
367 |
@{index_ML Rule_Insts.subgoal_tac: "Proof.context -> string -> |
|
368 |
(binding * string option * mixfix) list -> int -> tactic"} \\ |
|
369 |
@{index_ML Rule_Insts.thin_tac: "Proof.context -> string -> |
|
370 |
(binding * string option * mixfix) list -> int -> tactic"} \\ |
|
28785
64163cddf3e6
added section "Explicit instantiation within a subgoal context";
wenzelm
parents:
28783
diff
changeset
|
371 |
@{index_ML rename_tac: "string list -> int -> tactic"} \\ |
64163cddf3e6
added section "Explicit instantiation within a subgoal context";
wenzelm
parents:
28783
diff
changeset
|
372 |
\end{mldecls} |
64163cddf3e6
added section "Explicit instantiation within a subgoal context";
wenzelm
parents:
28783
diff
changeset
|
373 |
|
61854 | 374 |
\<^descr> @{ML Rule_Insts.res_inst_tac}~\<open>ctxt insts thm i\<close> instantiates the rule |
375 |
\<open>thm\<close> with the instantiations \<open>insts\<close>, as described above, and then performs |
|
376 |
resolution on subgoal \<open>i\<close>. |
|
28785
64163cddf3e6
added section "Explicit instantiation within a subgoal context";
wenzelm
parents:
28783
diff
changeset
|
377 |
|
61854 | 378 |
\<^descr> @{ML Rule_Insts.eres_inst_tac} is like @{ML Rule_Insts.res_inst_tac}, but |
379 |
performs elim-resolution. |
|
28785
64163cddf3e6
added section "Explicit instantiation within a subgoal context";
wenzelm
parents:
28783
diff
changeset
|
380 |
|
61854 | 381 |
\<^descr> @{ML Rule_Insts.dres_inst_tac} is like @{ML Rule_Insts.res_inst_tac}, but |
382 |
performs destruct-resolution. |
|
28785
64163cddf3e6
added section "Explicit instantiation within a subgoal context";
wenzelm
parents:
28783
diff
changeset
|
383 |
|
61439 | 384 |
\<^descr> @{ML Rule_Insts.forw_inst_tac} is like @{ML Rule_Insts.dres_inst_tac} |
59763 | 385 |
except that the selected assumption is not deleted. |
28785
64163cddf3e6
added section "Explicit instantiation within a subgoal context";
wenzelm
parents:
28783
diff
changeset
|
386 |
|
61854 | 387 |
\<^descr> @{ML Rule_Insts.subgoal_tac}~\<open>ctxt \<phi> i\<close> adds the proposition \<open>\<phi>\<close> as local |
388 |
premise to subgoal \<open>i\<close>, and poses the same as a new subgoal \<open>i + 1\<close> (in the |
|
389 |
original context). |
|
46271 | 390 |
|
61854 | 391 |
\<^descr> @{ML Rule_Insts.thin_tac}~\<open>ctxt \<phi> i\<close> deletes the specified premise from |
392 |
subgoal \<open>i\<close>. Note that \<open>\<phi>\<close> may contain schematic variables, to abbreviate |
|
393 |
the intended proposition; the first matching subgoal premise will be |
|
394 |
deleted. Removing useless premises from a subgoal increases its readability |
|
395 |
and can make search tactics run faster. |
|
46277 | 396 |
|
61854 | 397 |
\<^descr> @{ML rename_tac}~\<open>names i\<close> renames the innermost parameters of subgoal \<open>i\<close> |
398 |
according to the provided \<open>names\<close> (which need to be distinct identifiers). |
|
28785
64163cddf3e6
added section "Explicit instantiation within a subgoal context";
wenzelm
parents:
28783
diff
changeset
|
399 |
|
34930 | 400 |
|
61854 | 401 |
For historical reasons, the above instantiation tactics take unparsed string |
402 |
arguments, which makes them hard to use in general ML code. The slightly |
|
403 |
more advanced @{ML Subgoal.FOCUS} combinator of \secref{sec:struct-goals} |
|
404 |
allows to refer to internal goal structure with explicit context management. |
|
58618 | 405 |
\<close> |
28785
64163cddf3e6
added section "Explicit instantiation within a subgoal context";
wenzelm
parents:
28783
diff
changeset
|
406 |
|
64163cddf3e6
added section "Explicit instantiation within a subgoal context";
wenzelm
parents:
28783
diff
changeset
|
407 |
|
58618 | 408 |
subsection \<open>Rearranging goal states\<close> |
46274 | 409 |
|
61854 | 410 |
text \<open> |
411 |
In rare situations there is a need to rearrange goal states: either the |
|
412 |
overall collection of subgoals, or the local structure of a subgoal. Various |
|
413 |
administrative tactics allow to operate on the concrete presentation these |
|
414 |
conceptual sets of formulae. |
|
415 |
\<close> |
|
46274 | 416 |
|
58618 | 417 |
text %mlref \<open> |
46274 | 418 |
\begin{mldecls} |
419 |
@{index_ML rotate_tac: "int -> int -> tactic"} \\ |
|
46276 | 420 |
@{index_ML distinct_subgoals_tac: tactic} \\ |
58950
d07464875dd4
optional proof context for unify operations, for the sake of proper local options;
wenzelm
parents:
58618
diff
changeset
|
421 |
@{index_ML flexflex_tac: "Proof.context -> tactic"} \\ |
46274 | 422 |
\end{mldecls} |
423 |
||
61854 | 424 |
\<^descr> @{ML rotate_tac}~\<open>n i\<close> rotates the premises of subgoal \<open>i\<close> by \<open>n\<close> |
425 |
positions: from right to left if \<open>n\<close> is positive, and from left to right if |
|
426 |
\<open>n\<close> is negative. |
|
46274 | 427 |
|
61854 | 428 |
\<^descr> @{ML distinct_subgoals_tac} removes duplicate subgoals from a proof state. |
429 |
This is potentially inefficient. |
|
46276 | 430 |
|
61854 | 431 |
\<^descr> @{ML flexflex_tac} removes all flex-flex pairs from the proof state by |
432 |
applying the trivial unifier. This drastic step loses information. It is |
|
433 |
already part of the Isar infrastructure for facts resulting from goals, and |
|
434 |
rarely needs to be invoked manually. |
|
46276 | 435 |
|
436 |
Flex-flex constraints arise from difficult cases of higher-order |
|
61854 | 437 |
unification. To prevent this, use @{ML Rule_Insts.res_inst_tac} to |
438 |
instantiate some variables in a rule. Normally flex-flex constraints can be |
|
439 |
ignored; they often disappear as unknowns get instantiated. |
|
58618 | 440 |
\<close> |
46274 | 441 |
|
50074
0b02aaf7c7c5
some coverage of "resolution without lifting", which should be normally avoided;
wenzelm
parents:
50072
diff
changeset
|
442 |
|
58618 | 443 |
subsection \<open>Raw composition: resolution without lifting\<close> |
50074
0b02aaf7c7c5
some coverage of "resolution without lifting", which should be normally avoided;
wenzelm
parents:
50072
diff
changeset
|
444 |
|
58618 | 445 |
text \<open> |
61854 | 446 |
Raw composition of two rules means resolving them without prior lifting or |
447 |
renaming of unknowns. This low-level operation, which underlies the |
|
448 |
resolution tactics, may occasionally be useful for special effects. |
|
449 |
Schematic variables are not renamed by default, so beware of clashes! |
|
58618 | 450 |
\<close> |
50074
0b02aaf7c7c5
some coverage of "resolution without lifting", which should be normally avoided;
wenzelm
parents:
50072
diff
changeset
|
451 |
|
58618 | 452 |
text %mlref \<open> |
50074
0b02aaf7c7c5
some coverage of "resolution without lifting", which should be normally avoided;
wenzelm
parents:
50072
diff
changeset
|
453 |
\begin{mldecls} |
58956
a816aa3ff391
proper context for compose_tac, Splitter.split_tac (relevant for unify trace options);
wenzelm
parents:
58950
diff
changeset
|
454 |
@{index_ML compose_tac: "Proof.context -> (bool * thm * int) -> int -> tactic"} \\ |
52467 | 455 |
@{index_ML Drule.compose: "thm * int * thm -> thm"} \\ |
50074
0b02aaf7c7c5
some coverage of "resolution without lifting", which should be normally avoided;
wenzelm
parents:
50072
diff
changeset
|
456 |
@{index_ML_op COMP: "thm * thm -> thm"} \\ |
0b02aaf7c7c5
some coverage of "resolution without lifting", which should be normally avoided;
wenzelm
parents:
50072
diff
changeset
|
457 |
\end{mldecls} |
0b02aaf7c7c5
some coverage of "resolution without lifting", which should be normally avoided;
wenzelm
parents:
50072
diff
changeset
|
458 |
|
61854 | 459 |
\<^descr> @{ML compose_tac}~\<open>ctxt (flag, rule, m) i\<close> refines subgoal \<open>i\<close> using |
460 |
\<open>rule\<close>, without lifting. The \<open>rule\<close> is taken to have the form \<open>\<psi>\<^sub>1 \<Longrightarrow> \<dots> \<psi>\<^sub>m \<Longrightarrow> |
|
461 |
\<psi>\<close>, where \<open>\<psi>\<close> need not be atomic; thus \<open>m\<close> determines the number of new |
|
462 |
subgoals. If \<open>flag\<close> is \<open>true\<close> then it performs elim-resolution --- it solves |
|
463 |
the first premise of \<open>rule\<close> by assumption and deletes that assumption. |
|
50074
0b02aaf7c7c5
some coverage of "resolution without lifting", which should be normally avoided;
wenzelm
parents:
50072
diff
changeset
|
464 |
|
61854 | 465 |
\<^descr> @{ML Drule.compose}~\<open>(thm\<^sub>1, i, thm\<^sub>2)\<close> uses \<open>thm\<^sub>1\<close>, regarded as an |
466 |
atomic formula, to solve premise \<open>i\<close> of \<open>thm\<^sub>2\<close>. Let \<open>thm\<^sub>1\<close> and \<open>thm\<^sub>2\<close> be |
|
467 |
\<open>\<psi>\<close> and \<open>\<phi>\<^sub>1 \<Longrightarrow> \<dots> \<phi>\<^sub>n \<Longrightarrow> \<phi>\<close>. The unique \<open>s\<close> that unifies \<open>\<psi>\<close> and \<open>\<phi>\<^sub>i\<close> yields |
|
468 |
the theorem \<open>(\<phi>\<^sub>1 \<Longrightarrow> \<dots> \<phi>\<^sub>i\<^sub>-\<^sub>1 \<Longrightarrow> \<phi>\<^sub>i\<^sub>+\<^sub>1 \<Longrightarrow> \<dots> \<phi>\<^sub>n \<Longrightarrow> \<phi>)s\<close>. Multiple results are |
|
469 |
considered as error (exception @{ML THM}). |
|
50074
0b02aaf7c7c5
some coverage of "resolution without lifting", which should be normally avoided;
wenzelm
parents:
50072
diff
changeset
|
470 |
|
61854 | 471 |
\<^descr> \<open>thm\<^sub>1 COMP thm\<^sub>2\<close> is the same as \<open>Drule.compose (thm\<^sub>1, 1, thm\<^sub>2)\<close>. |
50074
0b02aaf7c7c5
some coverage of "resolution without lifting", which should be normally avoided;
wenzelm
parents:
50072
diff
changeset
|
472 |
|
0b02aaf7c7c5
some coverage of "resolution without lifting", which should be normally avoided;
wenzelm
parents:
50072
diff
changeset
|
473 |
|
0b02aaf7c7c5
some coverage of "resolution without lifting", which should be normally avoided;
wenzelm
parents:
50072
diff
changeset
|
474 |
\begin{warn} |
61854 | 475 |
These low-level operations are stepping outside the structure imposed by |
476 |
regular rule resolution. Used without understanding of the consequences, |
|
477 |
they may produce results that cause problems with standard rules and tactics |
|
478 |
later on. |
|
50074
0b02aaf7c7c5
some coverage of "resolution without lifting", which should be normally avoided;
wenzelm
parents:
50072
diff
changeset
|
479 |
\end{warn} |
58618 | 480 |
\<close> |
50074
0b02aaf7c7c5
some coverage of "resolution without lifting", which should be normally avoided;
wenzelm
parents:
50072
diff
changeset
|
481 |
|
0b02aaf7c7c5
some coverage of "resolution without lifting", which should be normally avoided;
wenzelm
parents:
50072
diff
changeset
|
482 |
|
58618 | 483 |
section \<open>Tacticals \label{sec:tacticals}\<close> |
18537 | 484 |
|
61854 | 485 |
text \<open> |
486 |
A \<^emph>\<open>tactical\<close> is a functional combinator for building up complex tactics |
|
487 |
from simpler ones. Common tacticals perform sequential composition, |
|
488 |
disjunctive choice, iteration, or goal addressing. Various search strategies |
|
489 |
may be expressed via tacticals. |
|
58618 | 490 |
\<close> |
46258 | 491 |
|
492 |
||
58618 | 493 |
subsection \<open>Combining tactics\<close> |
46258 | 494 |
|
61854 | 495 |
text \<open> |
496 |
Sequential composition and alternative choices are the most basic ways to |
|
497 |
combine tactics, similarly to ``\<^verbatim>\<open>,\<close>'' and ``\<^verbatim>\<open>|\<close>'' in Isar method notation. |
|
498 |
This corresponds to @{ML_op "THEN"} and @{ML_op "ORELSE"} in ML, but there |
|
499 |
are further possibilities for fine-tuning alternation of tactics such as |
|
500 |
@{ML_op "APPEND"}. Further details become visible in ML due to explicit |
|
46262 | 501 |
subgoal addressing. |
58618 | 502 |
\<close> |
46258 | 503 |
|
58618 | 504 |
text %mlref \<open> |
46258 | 505 |
\begin{mldecls} |
46262 | 506 |
@{index_ML_op "THEN": "tactic * tactic -> tactic"} \\ |
507 |
@{index_ML_op "ORELSE": "tactic * tactic -> tactic"} \\ |
|
508 |
@{index_ML_op "APPEND": "tactic * tactic -> tactic"} \\ |
|
46258 | 509 |
@{index_ML "EVERY": "tactic list -> tactic"} \\ |
510 |
@{index_ML "FIRST": "tactic list -> tactic"} \\[0.5ex] |
|
511 |
||
46262 | 512 |
@{index_ML_op "THEN'": "('a -> tactic) * ('a -> tactic) -> 'a -> tactic"} \\ |
513 |
@{index_ML_op "ORELSE'": "('a -> tactic) * ('a -> tactic) -> 'a -> tactic"} \\ |
|
514 |
@{index_ML_op "APPEND'": "('a -> tactic) * ('a -> tactic) -> 'a -> tactic"} \\ |
|
46258 | 515 |
@{index_ML "EVERY'": "('a -> tactic) list -> 'a -> tactic"} \\ |
516 |
@{index_ML "FIRST'": "('a -> tactic) list -> 'a -> tactic"} \\ |
|
517 |
\end{mldecls} |
|
518 |
||
61854 | 519 |
\<^descr> \<open>tac\<^sub>1\<close>~@{ML_op THEN}~\<open>tac\<^sub>2\<close> is the sequential composition of \<open>tac\<^sub>1\<close> and |
520 |
\<open>tac\<^sub>2\<close>. Applied to a goal state, it returns all states reachable in two |
|
521 |
steps by applying \<open>tac\<^sub>1\<close> followed by \<open>tac\<^sub>2\<close>. First, it applies \<open>tac\<^sub>1\<close> to |
|
522 |
the goal state, getting a sequence of possible next states; then, it applies |
|
523 |
\<open>tac\<^sub>2\<close> to each of these and concatenates the results to produce again one |
|
524 |
flat sequence of states. |
|
46258 | 525 |
|
61854 | 526 |
\<^descr> \<open>tac\<^sub>1\<close>~@{ML_op ORELSE}~\<open>tac\<^sub>2\<close> makes a choice between \<open>tac\<^sub>1\<close> and |
527 |
\<open>tac\<^sub>2\<close>. Applied to a state, it tries \<open>tac\<^sub>1\<close> and returns the result if |
|
528 |
non-empty; if \<open>tac\<^sub>1\<close> fails then it uses \<open>tac\<^sub>2\<close>. This is a deterministic |
|
529 |
choice: if \<open>tac\<^sub>1\<close> succeeds then \<open>tac\<^sub>2\<close> is excluded from the result. |
|
46258 | 530 |
|
61854 | 531 |
\<^descr> \<open>tac\<^sub>1\<close>~@{ML_op APPEND}~\<open>tac\<^sub>2\<close> concatenates the possible results of |
532 |
\<open>tac\<^sub>1\<close> and \<open>tac\<^sub>2\<close>. Unlike @{ML_op "ORELSE"} there is \<^emph>\<open>no commitment\<close> to |
|
533 |
either tactic, so @{ML_op "APPEND"} helps to avoid incompleteness during |
|
534 |
search, at the cost of potential inefficiencies. |
|
39852 | 535 |
|
61854 | 536 |
\<^descr> @{ML EVERY}~\<open>[tac\<^sub>1, \<dots>, tac\<^sub>n]\<close> abbreviates \<open>tac\<^sub>1\<close>~@{ML_op |
537 |
THEN}~\<open>\<dots>\<close>~@{ML_op THEN}~\<open>tac\<^sub>n\<close>. Note that @{ML "EVERY []"} is the same as |
|
538 |
@{ML all_tac}: it always succeeds. |
|
46258 | 539 |
|
61854 | 540 |
\<^descr> @{ML FIRST}~\<open>[tac\<^sub>1, \<dots>, tac\<^sub>n]\<close> abbreviates \<open>tac\<^sub>1\<close>~@{ML_op |
541 |
ORELSE}~\<open>\<dots>\<close>~@{ML_op "ORELSE"}~\<open>tac\<^sub>n\<close>. Note that @{ML "FIRST []"} is the |
|
542 |
same as @{ML no_tac}: it always fails. |
|
46258 | 543 |
|
61854 | 544 |
\<^descr> @{ML_op "THEN'"} is the lifted version of @{ML_op "THEN"}, for tactics |
545 |
with explicit subgoal addressing. So \<open>(tac\<^sub>1\<close>~@{ML_op THEN'}~\<open>tac\<^sub>2) i\<close> is |
|
546 |
the same as \<open>(tac\<^sub>1 i\<close>~@{ML_op THEN}~\<open>tac\<^sub>2 i)\<close>. |
|
46258 | 547 |
|
46264 | 548 |
The other primed tacticals work analogously. |
58618 | 549 |
\<close> |
30272 | 550 |
|
46259 | 551 |
|
58618 | 552 |
subsection \<open>Repetition tacticals\<close> |
46259 | 553 |
|
61854 | 554 |
text \<open> |
555 |
These tacticals provide further control over repetition of tactics, beyond |
|
556 |
the stylized forms of ``\<^verbatim>\<open>?\<close>'' and ``\<^verbatim>\<open>+\<close>'' in Isar method expressions. |
|
557 |
\<close> |
|
46259 | 558 |
|
58618 | 559 |
text %mlref \<open> |
46259 | 560 |
\begin{mldecls} |
561 |
@{index_ML "TRY": "tactic -> tactic"} \\ |
|
46266 | 562 |
@{index_ML "REPEAT": "tactic -> tactic"} \\ |
563 |
@{index_ML "REPEAT1": "tactic -> tactic"} \\ |
|
46259 | 564 |
@{index_ML "REPEAT_DETERM": "tactic -> tactic"} \\ |
565 |
@{index_ML "REPEAT_DETERM_N": "int -> tactic -> tactic"} \\ |
|
566 |
\end{mldecls} |
|
567 |
||
61854 | 568 |
\<^descr> @{ML TRY}~\<open>tac\<close> applies \<open>tac\<close> to the goal state and returns the resulting |
569 |
sequence, if non-empty; otherwise it returns the original state. Thus, it |
|
570 |
applies \<open>tac\<close> at most once. |
|
46259 | 571 |
|
61854 | 572 |
Note that for tactics with subgoal addressing, the combinator can be applied |
573 |
via functional composition: @{ML "TRY"}~@{ML_op o}~\<open>tac\<close>. There is no need |
|
574 |
for \<^verbatim>\<open>TRY'\<close>. |
|
46259 | 575 |
|
61854 | 576 |
\<^descr> @{ML REPEAT}~\<open>tac\<close> applies \<open>tac\<close> to the goal state and, recursively, to |
577 |
each element of the resulting sequence. The resulting sequence consists of |
|
578 |
those states that make \<open>tac\<close> fail. Thus, it applies \<open>tac\<close> as many times as |
|
46259 | 579 |
possible (including zero times), and allows backtracking over each |
61854 | 580 |
invocation of \<open>tac\<close>. @{ML REPEAT} is more general than @{ML REPEAT_DETERM}, |
581 |
but requires more space. |
|
46259 | 582 |
|
61854 | 583 |
\<^descr> @{ML REPEAT1}~\<open>tac\<close> is like @{ML REPEAT}~\<open>tac\<close> but it always applies \<open>tac\<close> |
584 |
at least once, failing if this is impossible. |
|
46259 | 585 |
|
61854 | 586 |
\<^descr> @{ML REPEAT_DETERM}~\<open>tac\<close> applies \<open>tac\<close> to the goal state and, |
587 |
recursively, to the head of the resulting sequence. It returns the first |
|
588 |
state to make \<open>tac\<close> fail. It is deterministic, discarding alternative |
|
589 |
outcomes. |
|
46266 | 590 |
|
61854 | 591 |
\<^descr> @{ML REPEAT_DETERM_N}~\<open>n tac\<close> is like @{ML REPEAT_DETERM}~\<open>tac\<close> but the |
592 |
number of repetitions is bound by \<open>n\<close> (where @{ML "~1"} means \<open>\<infinity>\<close>). |
|
58618 | 593 |
\<close> |
46259 | 594 |
|
61854 | 595 |
text %mlex \<open> |
596 |
The basic tactics and tacticals considered above follow some algebraic laws: |
|
46259 | 597 |
|
61854 | 598 |
\<^item> @{ML all_tac} is the identity element of the tactical @{ML_op "THEN"}. |
46259 | 599 |
|
61854 | 600 |
\<^item> @{ML no_tac} is the identity element of @{ML_op "ORELSE"} and @{ML_op |
601 |
"APPEND"}. Also, it is a zero element for @{ML_op "THEN"}, which means that |
|
602 |
\<open>tac\<close>~@{ML_op THEN}~@{ML no_tac} is equivalent to @{ML no_tac}. |
|
46259 | 603 |
|
61854 | 604 |
\<^item> @{ML TRY} and @{ML REPEAT} can be expressed as (recursive) functions over |
605 |
more basic combinators (ignoring some internal implementation tricks): |
|
58618 | 606 |
\<close> |
46259 | 607 |
|
58618 | 608 |
ML \<open> |
46259 | 609 |
fun TRY tac = tac ORELSE all_tac; |
610 |
fun REPEAT tac st = ((tac THEN REPEAT tac) ORELSE all_tac) st; |
|
58618 | 611 |
\<close> |
46259 | 612 |
|
61854 | 613 |
text \<open> |
614 |
If \<open>tac\<close> can return multiple outcomes then so can @{ML REPEAT}~\<open>tac\<close>. @{ML |
|
615 |
REPEAT} uses @{ML_op "ORELSE"} and not @{ML_op "APPEND"}, it applies \<open>tac\<close> |
|
616 |
as many times as possible in each outcome. |
|
46259 | 617 |
|
618 |
\begin{warn} |
|
61854 | 619 |
Note the explicit abstraction over the goal state in the ML definition of |
620 |
@{ML REPEAT}. Recursive tacticals must be coded in this awkward fashion to |
|
621 |
avoid infinite recursion of eager functional evaluation in Standard ML. The |
|
622 |
following attempt would make @{ML REPEAT}~\<open>tac\<close> loop: |
|
46259 | 623 |
\end{warn} |
58618 | 624 |
\<close> |
46259 | 625 |
|
59902 | 626 |
ML_val \<open> |
46260 | 627 |
(*BAD -- does not terminate!*) |
628 |
fun REPEAT tac = (tac THEN REPEAT tac) ORELSE all_tac; |
|
58618 | 629 |
\<close> |
46259 | 630 |
|
46263 | 631 |
|
58618 | 632 |
subsection \<open>Applying tactics to subgoal ranges\<close> |
46263 | 633 |
|
61854 | 634 |
text \<open> |
635 |
Tactics with explicit subgoal addressing @{ML_type "int -> tactic"} can be |
|
636 |
used together with tacticals that act like ``subgoal quantifiers'': guided |
|
637 |
by success of the body tactic a certain range of subgoals is covered. Thus |
|
638 |
the body tactic is applied to \<^emph>\<open>all\<close> subgoals, \<^emph>\<open>some\<close> subgoal etc. |
|
46263 | 639 |
|
61854 | 640 |
Suppose that the goal state has \<open>n \<ge> 0\<close> subgoals. Many of these tacticals |
641 |
address subgoal ranges counting downwards from \<open>n\<close> towards \<open>1\<close>. This has the |
|
642 |
fortunate effect that newly emerging subgoals are concatenated in the |
|
643 |
result, without interfering each other. Nonetheless, there might be |
|
644 |
situations where a different order is desired. |
|
645 |
\<close> |
|
46263 | 646 |
|
58618 | 647 |
text %mlref \<open> |
46263 | 648 |
\begin{mldecls} |
649 |
@{index_ML ALLGOALS: "(int -> tactic) -> tactic"} \\ |
|
650 |
@{index_ML SOMEGOAL: "(int -> tactic) -> tactic"} \\ |
|
651 |
@{index_ML FIRSTGOAL: "(int -> tactic) -> tactic"} \\ |
|
46267 | 652 |
@{index_ML HEADGOAL: "(int -> tactic) -> tactic"} \\ |
46263 | 653 |
@{index_ML REPEAT_SOME: "(int -> tactic) -> tactic"} \\ |
654 |
@{index_ML REPEAT_FIRST: "(int -> tactic) -> tactic"} \\ |
|
46267 | 655 |
@{index_ML RANGE: "(int -> tactic) list -> int -> tactic"} \\ |
46263 | 656 |
\end{mldecls} |
657 |
||
61854 | 658 |
\<^descr> @{ML ALLGOALS}~\<open>tac\<close> is equivalent to \<open>tac n\<close>~@{ML_op THEN}~\<open>\<dots>\<close>~@{ML_op |
659 |
THEN}~\<open>tac 1\<close>. It applies the \<open>tac\<close> to all the subgoals, counting downwards. |
|
46263 | 660 |
|
61854 | 661 |
\<^descr> @{ML SOMEGOAL}~\<open>tac\<close> is equivalent to \<open>tac n\<close>~@{ML_op ORELSE}~\<open>\<dots>\<close>~@{ML_op |
662 |
ORELSE}~\<open>tac 1\<close>. It applies \<open>tac\<close> to one subgoal, counting downwards. |
|
46263 | 663 |
|
61854 | 664 |
\<^descr> @{ML FIRSTGOAL}~\<open>tac\<close> is equivalent to \<open>tac 1\<close>~@{ML_op ORELSE}~\<open>\<dots>\<close>~@{ML_op |
665 |
ORELSE}~\<open>tac n\<close>. It applies \<open>tac\<close> to one subgoal, counting upwards. |
|
46263 | 666 |
|
61854 | 667 |
\<^descr> @{ML HEADGOAL}~\<open>tac\<close> is equivalent to \<open>tac 1\<close>. It applies \<open>tac\<close> |
668 |
unconditionally to the first subgoal. |
|
46267 | 669 |
|
61854 | 670 |
\<^descr> @{ML REPEAT_SOME}~\<open>tac\<close> applies \<open>tac\<close> once or more to a subgoal, counting |
671 |
downwards. |
|
46263 | 672 |
|
61854 | 673 |
\<^descr> @{ML REPEAT_FIRST}~\<open>tac\<close> applies \<open>tac\<close> once or more to a subgoal, counting |
674 |
upwards. |
|
46263 | 675 |
|
61854 | 676 |
\<^descr> @{ML RANGE}~\<open>[tac\<^sub>1, \<dots>, tac\<^sub>k] i\<close> is equivalent to \<open>tac\<^sub>k (i + k - |
677 |
1)\<close>~@{ML_op THEN}~\<open>\<dots>\<close>~@{ML_op THEN}~\<open>tac\<^sub>1 i\<close>. It applies the given list of |
|
678 |
tactics to the corresponding range of subgoals, counting downwards. |
|
58618 | 679 |
\<close> |
46263 | 680 |
|
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
681 |
|
58618 | 682 |
subsection \<open>Control and search tacticals\<close> |
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
683 |
|
61854 | 684 |
text \<open> |
685 |
A predicate on theorems @{ML_type "thm -> bool"} can test whether a goal |
|
686 |
state enjoys some desirable property --- such as having no subgoals. Tactics |
|
687 |
that search for satisfactory goal states are easy to express. The main |
|
688 |
search procedures, depth-first, breadth-first and best-first, are provided |
|
689 |
as tacticals. They generate the search tree by repeatedly applying a given |
|
690 |
tactic. |
|
691 |
\<close> |
|
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
692 |
|
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
693 |
|
46270 | 694 |
text %mlref "" |
695 |
||
58618 | 696 |
subsubsection \<open>Filtering a tactic's results\<close> |
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
697 |
|
58618 | 698 |
text \<open> |
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
699 |
\begin{mldecls} |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
700 |
@{index_ML FILTER: "(thm -> bool) -> tactic -> tactic"} \\ |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
701 |
@{index_ML CHANGED: "tactic -> tactic"} \\ |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
702 |
\end{mldecls} |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
703 |
|
61854 | 704 |
\<^descr> @{ML FILTER}~\<open>sat tac\<close> applies \<open>tac\<close> to the goal state and returns a |
705 |
sequence consisting of those result goal states that are satisfactory in the |
|
706 |
sense of \<open>sat\<close>. |
|
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
707 |
|
61854 | 708 |
\<^descr> @{ML CHANGED}~\<open>tac\<close> applies \<open>tac\<close> to the goal state and returns precisely |
709 |
those states that differ from the original state (according to @{ML |
|
710 |
Thm.eq_thm}). Thus @{ML CHANGED}~\<open>tac\<close> always has some effect on the state. |
|
58618 | 711 |
\<close> |
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
712 |
|
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
713 |
|
58618 | 714 |
subsubsection \<open>Depth-first search\<close> |
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
715 |
|
58618 | 716 |
text \<open> |
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
717 |
\begin{mldecls} |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
718 |
@{index_ML DEPTH_FIRST: "(thm -> bool) -> tactic -> tactic"} \\ |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
719 |
@{index_ML DEPTH_SOLVE: "tactic -> tactic"} \\ |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
720 |
@{index_ML DEPTH_SOLVE_1: "tactic -> tactic"} \\ |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
721 |
\end{mldecls} |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
722 |
|
61854 | 723 |
\<^descr> @{ML DEPTH_FIRST}~\<open>sat tac\<close> returns the goal state if \<open>sat\<close> returns true. |
724 |
Otherwise it applies \<open>tac\<close>, then recursively searches from each element of |
|
725 |
the resulting sequence. The code uses a stack for efficiency, in effect |
|
726 |
applying \<open>tac\<close>~@{ML_op THEN}~@{ML DEPTH_FIRST}~\<open>sat tac\<close> to the state. |
|
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
727 |
|
61854 | 728 |
\<^descr> @{ML DEPTH_SOLVE}\<open>tac\<close> uses @{ML DEPTH_FIRST} to search for states having |
729 |
no subgoals. |
|
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
730 |
|
61854 | 731 |
\<^descr> @{ML DEPTH_SOLVE_1}~\<open>tac\<close> uses @{ML DEPTH_FIRST} to search for states |
732 |
having fewer subgoals than the given state. Thus, it insists upon solving at |
|
733 |
least one subgoal. |
|
58618 | 734 |
\<close> |
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
735 |
|
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
736 |
|
58618 | 737 |
subsubsection \<open>Other search strategies\<close> |
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
738 |
|
58618 | 739 |
text \<open> |
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
740 |
\begin{mldecls} |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
741 |
@{index_ML BREADTH_FIRST: "(thm -> bool) -> tactic -> tactic"} \\ |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
742 |
@{index_ML BEST_FIRST: "(thm -> bool) * (thm -> int) -> tactic -> tactic"} \\ |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
743 |
@{index_ML THEN_BEST_FIRST: "tactic -> (thm -> bool) * (thm -> int) -> tactic -> tactic"} \\ |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
744 |
\end{mldecls} |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
745 |
|
61854 | 746 |
These search strategies will find a solution if one exists. However, they do |
747 |
not enumerate all solutions; they terminate after the first satisfactory |
|
748 |
result from \<open>tac\<close>. |
|
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
749 |
|
61854 | 750 |
\<^descr> @{ML BREADTH_FIRST}~\<open>sat tac\<close> uses breadth-first search to find states for |
751 |
which \<open>sat\<close> is true. For most applications, it is too slow. |
|
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
752 |
|
61854 | 753 |
\<^descr> @{ML BEST_FIRST}~\<open>(sat, dist) tac\<close> does a heuristic search, using \<open>dist\<close> |
754 |
to estimate the distance from a satisfactory state (in the sense of \<open>sat\<close>). |
|
755 |
It maintains a list of states ordered by distance. It applies \<open>tac\<close> to the |
|
756 |
head of this list; if the result contains any satisfactory states, then it |
|
757 |
returns them. Otherwise, @{ML BEST_FIRST} adds the new states to the list, |
|
758 |
and continues. |
|
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
759 |
|
61854 | 760 |
The distance function is typically @{ML size_of_thm}, which computes the |
761 |
size of the state. The smaller the state, the fewer and simpler subgoals it |
|
762 |
has. |
|
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
763 |
|
61854 | 764 |
\<^descr> @{ML THEN_BEST_FIRST}~\<open>tac\<^sub>0 (sat, dist) tac\<close> is like @{ML BEST_FIRST}, |
765 |
except that the priority queue initially contains the result of applying |
|
766 |
\<open>tac\<^sub>0\<close> to the goal state. This tactical permits separate tactics for |
|
767 |
starting the search and continuing the search. |
|
58618 | 768 |
\<close> |
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
769 |
|
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
770 |
|
58618 | 771 |
subsubsection \<open>Auxiliary tacticals for searching\<close> |
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
772 |
|
58618 | 773 |
text \<open> |
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
774 |
\begin{mldecls} |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
775 |
@{index_ML COND: "(thm -> bool) -> tactic -> tactic -> tactic"} \\ |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
776 |
@{index_ML IF_UNSOLVED: "tactic -> tactic"} \\ |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
777 |
@{index_ML SOLVE: "tactic -> tactic"} \\ |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
778 |
@{index_ML DETERM: "tactic -> tactic"} \\ |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
779 |
\end{mldecls} |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
780 |
|
61854 | 781 |
\<^descr> @{ML COND}~\<open>sat tac\<^sub>1 tac\<^sub>2\<close> applies \<open>tac\<^sub>1\<close> to the goal state if it |
782 |
satisfies predicate \<open>sat\<close>, and applies \<open>tac\<^sub>2\<close>. It is a conditional tactical |
|
783 |
in that only one of \<open>tac\<^sub>1\<close> and \<open>tac\<^sub>2\<close> is applied to a goal state. However, |
|
784 |
both \<open>tac\<^sub>1\<close> and \<open>tac\<^sub>2\<close> are evaluated because ML uses eager evaluation. |
|
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
785 |
|
61854 | 786 |
\<^descr> @{ML IF_UNSOLVED}~\<open>tac\<close> applies \<open>tac\<close> to the goal state if it has any |
787 |
subgoals, and simply returns the goal state otherwise. Many common tactics, |
|
788 |
such as @{ML resolve_tac}, fail if applied to a goal state that has no |
|
789 |
subgoals. |
|
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
790 |
|
61854 | 791 |
\<^descr> @{ML SOLVE}~\<open>tac\<close> applies \<open>tac\<close> to the goal state and then fails iff there |
792 |
are subgoals left. |
|
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
793 |
|
61854 | 794 |
\<^descr> @{ML DETERM}~\<open>tac\<close> applies \<open>tac\<close> to the goal state and returns the head of |
795 |
the resulting sequence. @{ML DETERM} limits the search space by making its |
|
796 |
argument deterministic. |
|
58618 | 797 |
\<close> |
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
798 |
|
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
799 |
|
58618 | 800 |
subsubsection \<open>Predicates and functions useful for searching\<close> |
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
801 |
|
58618 | 802 |
text \<open> |
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
803 |
\begin{mldecls} |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
804 |
@{index_ML has_fewer_prems: "int -> thm -> bool"} \\ |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
805 |
@{index_ML Thm.eq_thm: "thm * thm -> bool"} \\ |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
806 |
@{index_ML Thm.eq_thm_prop: "thm * thm -> bool"} \\ |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
807 |
@{index_ML size_of_thm: "thm -> int"} \\ |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
808 |
\end{mldecls} |
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
809 |
|
61854 | 810 |
\<^descr> @{ML has_fewer_prems}~\<open>n thm\<close> reports whether \<open>thm\<close> has fewer than \<open>n\<close> |
811 |
premises. |
|
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
812 |
|
61854 | 813 |
\<^descr> @{ML Thm.eq_thm}~\<open>(thm\<^sub>1, thm\<^sub>2)\<close> reports whether \<open>thm\<^sub>1\<close> and \<open>thm\<^sub>2\<close> are |
814 |
equal. Both theorems must have the same conclusions, the same set of |
|
815 |
hypotheses, and the same set of sort hypotheses. Names of bound variables |
|
816 |
are ignored as usual. |
|
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
817 |
|
61854 | 818 |
\<^descr> @{ML Thm.eq_thm_prop}~\<open>(thm\<^sub>1, thm\<^sub>2)\<close> reports whether the propositions of |
819 |
\<open>thm\<^sub>1\<close> and \<open>thm\<^sub>2\<close> are equal. Names of bound variables are ignored. |
|
820 |
||
821 |
\<^descr> @{ML size_of_thm}~\<open>thm\<close> computes the size of \<open>thm\<close>, namely the number of |
|
822 |
variables, constants and abstractions in its conclusion. It may serve as a |
|
823 |
distance function for @{ML BEST_FIRST}. |
|
58618 | 824 |
\<close> |
46269
e75181672150
updated "Control and search tacticals" (moved from ref to implementation);
wenzelm
parents:
46267
diff
changeset
|
825 |
|
18537 | 826 |
end |