:clause-processor rule (goal-level simplifier)
Major Section: RULE-CLASSES
Also see define-trusted-clause-processor for documentation of an analogous
utility that does not require the clause-processor to be proved correct. But
please read the present documentation before reading about that utility.
Both utilities designate functions ``clause-processors''. Such functions
must be executable -- hence not constrained by virtue of being introduced
in the signature of an encapsulate -- and must respect
stobj and output arity restrictions. For example, something like
(car (mv ...)) is illegal; also see signature.
We begin this documentation with an introduction, focusing on an example, and
then conclude with details. You might find it most useful simply to look at
the examples in distributed directory books/clause-processors/; see file
Readme.lsp in that directory.
A :clause-processor rule installs a simplifier at the level of goals,
where a goal is represented as a clause: a list of terms that is
implicitly viewed as a disjunction (the application of OR). For
example, if ACL2 prints a goal in the form (implies (and p q) r), then
the clause might be the one-element list containing the internal
representation of this term -- (implies (if p q 'nil) r) -- but more
likely, the corresponding clause is ((not p) (not q) r). Note that the
members of a clause are translated terms; see term. For example, they
do not contains calls of the macro AND, and constants are quoted.
Note that clause-processor simplifiers are similar to metafunctions, and similar efficiency considerations apply. See meta, in particular the discussion on how to ``make a metafunction maximally efficient.''
Unlike rules of class :meta, rules of class :clause-processor
must be applied by explicit :clause-processor hints; they are not
applied automatically (unless by way of computed hints; see computed-hints).
But :clause-processor rules can be useful in situations for which it is
more convenient to code a simplifier that manipulates the entire goal clause
rather than individual subterms of terms in the clause.
We begin with a simple illustrative example: a clause-processor that assumes
an alleged fact and creates a separate goal to prove that fact. We can
extend the hypotheses of the current goal with a term by adding the negation
of that term to the clause (disjunctive) representation of that goal. So the
following returns two clauses: the result of adding term as a hypothesis
to the input clause, as just described, and a second clause consisting only
of that term.
As with(defun note-fact-clause-processor (cl term) (declare (xargs :guard t)) ; optional, for better efficiency (list (cons (list 'not term) cl) (list term)))
:meta rules, we need to introduce a suitable evaluator;
see defevaluator if you want details. Since we expect to reason about the
function NOT, because of its role in note-fact-clause-processor as
defined above, we include NOT in the set of functions known to this
evaluator. We also include IF, as is often a good idea.
ACL2 can now prove the following theorem automatically. (Of course,(defevaluator evl0 evl0-list ((not x) (if x y z)))
:clause-processor rules about clause-processor functions less trivial
than note-fact-clause-processor may require lemmas to be proved first!)
The function disjoin takes a clause and returns its disjunction (the
result of applying OR to its members), and conjoin-clauses applies
disjoin to every element of a given list of clauses and then conjoins
(applies AND) to the corresponding list of resulting terms.
Now let us submit a silly but illustrative example theorem to ACL2, to show how a corresponding(defthm correctness-of-note-fact-clause-processor (implies (and (pseudo-term-listp cl) (alistp a) (evl0 (conjoin-clauses (note-fact-clause-processor cl term)) a)) (evl0 (disjoin cl) a)) :rule-classes :clause-processor)
:clause-processor hint is applied. The hint says to
apply the clause-processor function, note-fact-clause-processor, to the
current goal clause and a ``user hint'' as the second argument of that
function, in this case (equal a a). Thus, a specific variable,
clause, is always bound to the current goal clause for the evaluation of
the :clause-processor hint, to produce a list of clauses. Since two
subgoals are created below, we know that this list contained two clauses.
Indeed, these are the clauses returned when note-fact-clause-processor is
applied to two arguments: the current clause, which is the one-element list
((equal (car (cons x y)) x)), and the user hint, (equal a a).
ACL2 !>(thm (equal (car (cons x y)) x) :hints (("Goal" :clause-processor (note-fact-clause-processor clause '(equal a a)))))
[Note: A hint was supplied for our processing of the goal above. Thanks!]
We now apply the verified :CLAUSE-PROCESSOR function NOTE-FACT-CLAUSE- PROCESSOR to produce two new subgoals.
Subgoal 2 (IMPLIES (EQUAL A A) (EQUAL (CAR (CONS X Y)) X)).
But we reduce the conjecture to T, by the :executable-counterpart of IF and the simple :rewrite rule CAR-CONS.
Subgoal 1 (EQUAL A A).
But we reduce the conjecture to T, by primitive type reasoning.
Q.E.D.
Summary Form: ( THM ...) Rules: ((:EXECUTABLE-COUNTERPART IF) (:EXECUTABLE-COUNTERPART NOT) (:FAKE-RUNE-FOR-TYPE-SET NIL) (:REWRITE CAR-CONS)) Warnings: None Time: 0.00 seconds (prove: 0.00, print: 0.00, other: 0.00)
Proof succeeded. ACL2 !>
That concludes our introduction to clause-processor rules and hints. We turn now to detailed documentation.
The signature of a clause-processor function, CL-PROC, must have
one of the following forms. Here, each st_i is a stobj (possibly
state) while the other parameters and results are not stobjs
(see defstobj). Note that there need not be input stobjs in [3] -- i.e.,
k can be 0 -- and even if there are, there need not be output stobjs.
[1] ((CL-PROC cl) => cl-list)In [3], we think of the first component of the result as an error flag. Indeed, a proof will instantly abort if that error flag is not[2] ((CL-PROC cl hint) => cl-list)
[3] ((CL-PROC cl hint st_1 ... st_k) => (erp cl-list st_i1 ... st_in))
nil.
We next discuss the legal forms of :clause-processor rules, followed
below by a discussion of :clause-processor hints. In the discussion
below, we use lower-case names to represent specific symbols, for example
implies, and we use upper-case names to represent more arbitrary pieces
of syntax (which we will describe), for example, CL.
If a :rule-classes specification includes :clause-processor,
then the corresponding term must have the form
(implies (and (pseudo-term-listp CL)
(alistp A)
(EVL (conjoin-clauses <CL-LIST>)
B))
(EVL (disjoin CL) A))
where: EVL is a known evaluator; CL and A are distinct non-stobj
variables; and <CL-LIST> is an expression representing the clauses
returned by the clause-processor function CL-PROC, whose form depends on
the signature of that function, as follows. Typically B is A,
but it can be any term (useful when generalization is occurring; see the
example ``Test generalizing alist'' in
books/clause-processors/basic-examples.lisp). For cases [1] and [2]
above, <CL-LIST> is of the form (CL-PROC CL) or
(CL-PROC CL HINT), respectively, where in the latter case HINT is a
non-stobj variable distinct from the variables CL and A. For case
[3], <CL-LIST> is of the form
(clauses-result (CL-PROC CL HINT st_1 ... st_k))where the
st_i are the specific stobj names mentioned in [3]. Logically,
clauses-result returns the cadr if the car is NIL, and
otherwise (for the error case) returns a list containing the empty (false)
clause. So in the non-error case, clauses-result picks out the second
result, denoted cl-list in [3] above, and in the error case the
implication above trivially holds.
In the above theorem, we are asked to prove (EVL (disjoin CL) A) assuming
that the conjunction of all clauses produced by the clause processor
evaluates to a non-nil value under some alist B. In fact, we can
choose B so as to allow us to assume evaluations of the generated clauses
over many different alists. This technique is discussed in the distributed
book books/clause-processors/multi-env-trick.lisp, which introduces some
macros that may be helpful in accomplishing proofs of this type.
The clause-processor function, CL, must have a guard that ACL2 can
trivially prove from the hypotheses that the first argument of CL is
known to be a pseudo-term-listp and any stobj arguments are assumed
to satisfy their stobj predicates.
Next we specify the legal forms for :clause-processor hints. These
depend on the signature as described in [1] through [3] above. Below, as
above, CL-PROC is the clause-processor function, and references to
``clause'' refer to that exact variable (not, for example, to cl).
In each of the three cases, the forms shown for that case are equivalent; in
particular, the :function syntax is simply a convenience for the final
form in each case.
Signature [1], ((cl-proc cl) => cl-list):
:clause-processor CL-PROC :clause-processor (:function CL-PROC) :clause-processor (CL-PROC clause)or any term macroexpanding to
(CL-PROC clause).Signature [2], ((cl-proc cl hint) => cl-list):
:clause-processor (:function CL-PROC :hint HINT) :clause-processor (CL-PROC clause HINT)or any term macroexpanding to
(CL-PROC clause HINT), where HINT is
any term with at most CLAUSE free.Signature [3], ((CL-PROC cl hint ...) => (erp cl-list ...))
:clause-processor (:function CL-PROC :hint HINT) :clause-processor (CL-PROC clause HINT st_1 ... st_k)or any term macroexpanding to
(CL-PROC clause HINT st_1 ... st_k), where
HINT is any term with at most CLAUSE free.
A :clause-processor hint causes the proof to abort if the result returned
by evaluating the suitable CL-PROC call, as above, is not a list of
clauses, i.e., a list of (translated) term lists. The proof also aborts
if in case [3] the first (erp) value returned is not nil, in which
case erp is used for printing an error message as follows: if it is a
string, then that string is printed; but if it is a non-empty true list whose
first element is a string, then it is printed as though by
(fmt ~@0 (list (cons #\0 erp)) ...) (see fmt). Otherwise, a
non-nil erp value causes a generic error message to be printed.
If there is no error as above, but the CL-PROC call returns clause list
whose single element is equal to the input clause, then the hint is ignored
since we are left with the goal with which we started. In that case, the
other prover processes are then applied as usual.
You can see all current :clause-processor rules by issuing the following
command: (print-clause-processor-rules).
The following paper discusses ACL2 clause-processors at a high level suitable for a non-ACL2 audience:
M. Kaufmann, J S. Moore, S. Ray, and E. Reeber, ``Integrating External Deduction Tools with ACL2.'' Journal of Applied Logic (Special Issue: Empirically Successful Computerized Reasoning), Volume 7, Issue 1, March 2009, pp. 3--25. Also published online (DOI
10.1016/j.jal.2007.07.002). Preliminary version in: Proceedings of the 6th International Workshop on the Implementation of Logics (IWIL 2006) (C. Benzmueller, B. Fischer, and G. Sutcliffe, editors), CEUR Workshop Proceedings Vol. 212, Phnom Penh, Cambodia, pp. 7-26, November 2006, http://ceur-ws.org/Vol-212/.