allowing a name to be introduced ``twice''
Parent topic: MISCELLANEOUS Home
Sometimes an event will announce that it is ``redundant''. When this happens, no change to the logical world has occurred. This happens when the logical name being defined is already defined and has exactly the same definition, from the logical point of view. This feature permits two independent books, each of which defines some name, to be included sequentially provided they use exactly the same definition.
When are two logical-name definitions considered exactly the same? It depends upon the kind of name being defined.
A deflabel
event is never redundant. This means that if you have a
deflabel
in a book and that book has been included (without error),
then references to that label denote the point in history at which
the book introduced the label. See the note about shifting logical
names, below.
A defun
or mutual-recursion
(or defuns
) event is redundant if for
each function to be introduced, there has already been introduced a
function with the same name, the same formals, and syntactically
identical guard
, type declarations, and body
(before
macroexpansion).
A verify-guards
event is redundant if the function has already had
its guards verified.
A defaxiom
or defthm
event is redundant if there is already an axiom
or theorem of the given name and both the formula (after
macroexpansion) and the rule-classes are syntactically identical.
Note that a defaxiom
can make a subsequent defthm
redundant, and a
defthm
can make a subsequent defaxiom
redundant as well.
A defconst
is redundant if the name has been defined to have the
same value.
A defmacro
is redundant if there is already a macro defined with the
same name and syntactically identical arguments, guard, and body.
A defpkg
is redundant if a package of the same name with exactly the
same imports has been defined.
A deftheory
is never redundant. The ``natural'' notion of
equivalent deftheory
s is that the names and values of the two theory
expressions are the same. But since most theory expressions are
sensitive to the context in which they occur, it seems unlikely to
us that two deftheory
s coming from two sequentially included books
will ever have the same values. So we prohibit redundant theory
definitions. If you try to define the same theory name twice, you
will get a ``name in use'' error.
An in-theory
event is never redundant because it doesn't define any
name.
Table
and defdoc
events are never redundant because they don't
define any name.
An encapsulate
event is redundant if and only if a syntactically
identical encapsulate
has already been executed under the same
default-defun-mode
.
An include-book
is redundant if the book has already been included.
Note About Shifting Logical Names:
Suppose a book defines a function fn
and later uses fn
as a logical
name in a theory expression. Consider the value of that theory
expression in two different sessions. In session A, the book is
included in a world in which fn
is not already defined, i.e., in a
world in which the book's definition of fn
is not redundant. In
session B, the book is included in a world in which fn
is already
identically defined. In session B, the book's definition of fn
is
redundant. When fn
is used as a logical name in a theory
expression, it denotes the point in history at which fn
was
introduced. Observe that those points are different in the two
sessions. Hence, it is likely that theory expressions involving fn
will have different values in session A than in session B.
This may adversely affect the user of your book. For example,
suppose your book creates a theory via deftheory
that is advertised
just to contain the names generated by the book. But suppose you
compute the theory as the very last event in the book using:
(set-difference-theories (universal-theory :here) (universal-theory fn))where
fn
is the very first event in the book and happens to be a
defun
event. This expression returns the advertised set if fn
is
not already defined when the book is included. But if fn
were
previously (identically) defined, the theory is larger than
advertised.
The moral of this is simple: when building books that other people
will use, it is best to describe your theories in terms of logical
names that will not shift around when the books are included. The
best such names are those created by deflabel
.
saving and restoring your logical state
Parent topic: MISCELLANEOUS Home
Examples: ACL2 !>:Q >(make-lib "file") ... >(note-lib "file") >(LP) ACL2 !>
To save the current ACL2 logical world to a file, exit ACL2 with :
q
and invoke (make-lib "file")
in Common Lisp. This creates a file
"file.lib"
and a file "file.lisp"
. The latter will be compiled.
It generally takes half an hour to save an ACL2 logical world and
creates a 20Mb file. All things considered it is probably better to
just save your core image.
To restore such a saved ACL2 world, invoke (note-lib "file")
from
Common Lisp, and then enter ACL2 with (lp)
. We do not save the io
system, the stacks, or the global table, hence bindings of your
globals will not be restored.
This save/restore mechanism is a temporary expedient. We know of faster mechanisms, mechanisms that consume less disk space, and mechanisms that provide more functionality. We don't know of good compromises between these various desirable features.
how to specify the arity of a constrained function
Parent topic: MISCELLANEOUS Home
Examples: (hd (x) t) (printer (x state) (mv t t state)) (printer (x state) (mv er-flg val state))whereGeneral Form: (fn formals result)
fn
is the constrained function symbol, formals
is a suitable
list of formal parameters for it, and result
is either a symbol
denoting that the function returns one result (which is a state
object or not depending on whether the symbol is state
) or else
result is an mv
expression, (mv s1 ... sn)
, where n>1
, each si
is a
symbol, and at most one of the si
is the symbol state
. The latter
form of result indicates that the function returns n
results and
indicates which of them (if any) is a state object. The non-state
si
are just place holders and may all be identical, e.g., t
, though
we often use symbols that suggest the type of the corresponding
value. It is illegal for state
to be used in result if state
does
not appear in formals
.
:
definition
and :
rewrite
rules used in preprocessing
Parent topic: MISCELLANEOUS Home
Example of simple rewrite rule: (equal (car (cons x y)) x)Examples of simple definition: (defun file-clock-p (x) (integerp x)) (defun naturalp (x) (and (integerp x) (>= x 0)))
The theorem prover output sometimes refers to ``simple'' definitions
and rewrite rules. These rules can be used by the preprocessor,
which is one of the theorem prover's ``processes'' understood by the
:do-not
hint; see hints.
The preprocessor expands certain definitions and uses certain
rewrite rules that it considers to be ``fast''. There are two ways
to qualify as fast. One is to be an ``abbreviation'', where a
rewrite rule with no hypotheses or loop stopper is an
``abbreviation'' if the right side contains no more variable
occurrences than the left side, and the right side does not call the
functions if
, not
or implies
. Definitions and rewrite rules can
both be abbreviations; the criterion for definitions is similar,
except that the definition must not be recursive. The other way to
qualify applies only to a non-recursive definition, and applies when
its body is a disjunction or conjunction, according to a perhaps
subtle criterion that is intended to avoid case splits.
nonproductive proof steps
Parent topic: MISCELLANEOUS Home
Occasionally the ACL2 theorem prover reports that the current goal simplifies to itself or to a set including itself. Such simplifications are said to be ``specious'' and are ignored in the sense that the theorem prover acts as though no simplification were possible and tries the next available proof technique. Specious simplifications are almost always caused by forcing.
The simplification of a formula proceeds primarily by the local
application of :
rewrite
, :
type-prescription
, and other rules to its
various subterms. If no rewrite rules apply, the formula cannot be
simplified and is passed to the next ACL2 proof technique, which is
generally the elimination of destructors. The experienced ACL2 user
pays special attention to such ``maximally simplified'' formulas;
the presence of unexpected terms in them indicates the need for
additional rules or the presence of some conflict that prevents
existing rules from working harmoniously together.
However, consider the following interesting possibility: local
rewrite rules apply but, when applied, reproduce the goal as one of
its own subgoals. How can rewrite rules apply and reproduce the
goal? Of course, one way is for one rule application to undo the
effect of another, as when commutativity is applied twice in
succession to the same term. Another kind of example is when rules
conflict and undermine each other. For example, under suitable
hypotheses, (length x)
might be rewritten to (+ 1 (length (cdr x)))
by the :
definition
of length
and then a :
rewrite
rule might be used
to ``fold'' that back to (length x)
. Generally speaking the
presence of such ``looping'' rewrite rules causes ACL2's simplifier
either to stop gracefully because of heuristics such as that
described in the documentation for loop-stopper
or to cause a
stack overflow because of indefinite recursion.
A more insidious kind of loop can be imagined: two rewrites in different parts of the formula undo each other's effects ``at a distance,'' that is, without ever being applied to one another's output. For example, perhaps the first hypothesis of the formula is simplified to the second, but then the second is simplified to the first, so that the end result is a formula propositionally equivalent to the original one but with the two hypotheses commuted. This is thought to be impossible unless forcing occurs, but if forcing is exploited (see force) it can be made to happen relatively easily.
Here is a simple example. Declare foo
to be a function of one
argument returning one result:
(defstub foo (x) t)Add the following
:
type-prescription
rule about foo
:
(defaxiom forcer (implies (force (not (true-listp x))) (equal (foo x) t)) :rule-classes :type-prescription)Note that we could define a
foo
with this property; defstub
and
defaxiom
are only used here to get to the gist of the problem
immediately. Consider the proof attempt for the following formula.
(thm (implies (and (consp x) ; hyp 1 (true-listp (cdr x)) ; hyp 2 (true-listp x)) ; hyp 3 (foo x))) ; conclWhen we simplify this goal,
hyp 1
cannot be simplified. Hyp 2
simplifies to t
, because x
is known to be a non-nil
true list so its
cdr
is a true list by type reasoning; because true hypotheses are
dropped, hyp 2
simply disappears. Hyp 3
simplifies to
(true-listp (cdr x))
by opening up the :
definition
of
true-listp
. Note that hyp 3
has simplified to the old hyp 2
.
So at this point, the ``current (intermediate) goal'' is
(implies (and (consp x) ; rewritten hyp 1 (true-listp (cdr x))) ; rewritten hyp 3 (foo x)) ; unrewritten concland we are working on
(foo x)
. But the :
type-prescription
rule
above tells us that (foo x)
is t
if the hypothesis of the rule is
true. Thus, in the case that the hypothesis of the rule is true, we
are done. It remains to prove the current intermediate goal under
the assumption that the hypothesis of the rule is false. This is
done by adding the negation of the :
type-prescription
rule's
hypothesis to the current intermediate goal. This is what force
does in this situation. The negation of the hypothesis is
(true-listp x)
. Adding it to the current goal produces the subgoal
(implies (and (consp x) ; rewritten hyp 1 (true-listp (cdr x)) ; rewritten hyp 3 (true-listp x)) ; FORCEd hyp (foo x)). ; unrewritten conclObserve that this is just our original goal. Despite all the rewriting, no progress was made! In more common cases, the original goal may simplify to a set of subgoals, one of which includes the original goal.
If ACL2 were to adopt the new set of subgoals, it would loop indefinitely. Therefore, it checks whether the input goal is a member of the output subgoals. If so, it announces that the simplification is ``specious'' and pretends that no simplification occurred.
``Maximally simplified'' formulas that produce specious simplifications are maximally simplified in a very technical sense: were ACL2 to apply every applicable rule to them, no progress would be made. Since ACL2 can only apply every applicable rule, it cannot make further progress with the formula. But the informed user can perhaps identify some rule that should not be applied and make it inapplicable by disabling it, allowing the simplifier to apply all the others and thus make progress.
When specious simplifications are a problem it might be helpful to disable all forcing and resubmit the formula to observe whether forcing is involved in the loop or not. See force. The commands
ACL2 !>:disable-forcing and ACL2 !>:enable-forcingdisable and enable forcing. If the loop is broken when forcing is disabled, then it is very likely some forced hypothesis of some rule is ``undoing'' a prior simplification. The most common cause of this is when we force a hypothesis that is actually false but whose falsity is somehow temporarily hidden (more below). To find the offending rule, compare the specious simplification with its non-specious counterpart and look for rules that were speciously applied that are not applied in the non-specious case. Most likely you will find at least one such rule and it will have a
force
d
hypothesis. By disabling that rule, at least for the subgoal in
question, you may allow the simplifier to make progress on the
subgoal.
To illustrate what we mean by the claim that specious
simplifications often arise because the system forces a false
hypothesis, reconsider the example above. At the time we used the
:
type-prescription
rule, the known assumptions were (consp x)
and
(true-listp (cdr x))
. Observe that this tells us that x
is a true
list. But the hypothesis forced to be true was
(not (true-listp x))
. Why was the falsity of this hypothesis
missed? The most immediate reason is that the encoding of the two
assumptions above does not produce a context (``type-alist'') in
which x
is recorded to be a true-list. When we look up
(not (true-listp x))
in that context, we are not told that it is
false. More broadly, the problem stems from the fact that when we
force a hypothesis we do not bring to bear on it all of the
resources of the theorem prover. Thus it could be -- as here --
that the hypothesis could be proved false in the current context but
is not obviously so. No matter how sophisticated we made the
forcing mechanism, the unavoidable incompleteness of the theorem
prover would still permit the occasional specious simplification.
While that does not excuse us from trying to avoid specious
simplifications when we can -- and we may well strengthen the type
mechanism to deal with the problem illustrated here -- specious
simplifications will probably remain a problem deserving of the
user's attention.
the von Neumannesque ACL2 state object
Parent topic: MISCELLANEOUS Home
The ACL2 state object is used extensively in programming the ACL2 system, and has been used in other ACL2 programs as well. However, most users, especially those interested in specification and verification (as opposed to programming per se), need not be aware of the role of the state object in ACL2, and will not write functions that use it explicitly. We say more about this point at the end of this documentation topic.
The global table is perhaps the most visible portion of the state
object. Using the interface functions @
and assign
, a user
will bind global variables to the results of function evaluations
(much as an Nqthm user exploits the Nqthm utility r-loop
).
See @ and see assign.
ACL2 supports several facilities of a truly von Neumannesque state
machine character, including file io and global variables.
Logically speaking, the state is a true list of the 14 components as
described below. There is a ``current'' state object at the
top-level of the ACL2 command loop. This object is understood to be
the value of what would otherwise be the free variable state
appearing in top-level input. When any command returns a state
object as one of its values, that object becomes the new current
state. But ACL2 provides von Neumann style speed for state
operations by maintaining only one physical (as opposed to logical)
state object. Operations on the state are in fact destructive.
This implementation does not violate the applicative semantics
because we enforce certain draconian syntactic rules regarding the
use of state objects. For example, one cannot ``hold on'' to an old
state, access the components of a state arbitrarily, or ``modify'' a
state object without passing it on to subsequent state-sensitive
functions.
Every routine that uses the state facilities (e.g. does io, or calls
a routine that does io), must be passed a ``state object.'' And a
routine must return a state object if the routine modifies the state
in any way. Rigid syntactic rules governing the use of state
objects are enforced by the function translate
, through which all
ACL2 user input first passes. State objects can only be ``held'' in
the formal parameter state
, never in any other formal parameter and
never in any structure (excepting a multiple-values return list
field which is always a state object). State objects can only be
accessed with the primitives we specifically permit. Thus, for
example, one cannot ask, in code to be executed, for the length of
state
or the car
of state
. In the statement and proof of theorems,
there are no syntactic rules prohibiting arbitrary treatment of
state objects.
Logically speaking, a state object is a true list whose members are as follows:
Open-input-channels
, an alist with keys that are symbols in package"ACL2-INPUT-CHANNEL"
. The value (cdr
) of each pair has the form((:header type file-name open-time) . elements)
, wheretype
is one of:character
,:byte
, or:object
andelements
is a list of things of the correspondingtype
, i.e. characters, integers of type(mod 255)
, or lisp objects in our theory.File-name
is a string.Open-time
is an integer. See io.
Open-output-channels
, an alist with keys that are symbols in package"ACL2-OUTPUT-CHANNEL"
. The value of a pair has the form((:header type file-name open-time) . current-contents)
. See io.
Global-table
, an alist associating symbols (to be used as ``global variables'') with values. See @, and see assign.
T-stack
, a list of arbitrary objects accessed and changed by the functionsaref-t-stack
andaset-t-stack
.
32-bit-integer-stack
, a list of arbitrary 32-bit-integers accessed and changed by the functionsaref-32-bit-integer-stack
andaset-32-bit-integer-stack
.
Big-clock-entry
, an integer, that is used logically to bound the amount of effort spent to evaluate a quoted form.
Idates
, a list of dates and times, used to implement the functionprint-current-idate
, which prints the date and time.
Run-times
, a list of integers, used to implement the functions that let ACL2 report how much time was used, but inaccessible to the user.
File-clock
, an integer that is increased on every file opening and closing and used to maintain the consistency of theio
primitives.
Readable-files
, an alist whose keys have the form(string type time)
, wherestring
is a file name andtime
is an integer. The value associated with such a key is a list of characters, bytes, or objects, according totype
. Thetime
field is used in the following way: when it comes time to open a file for input, we will only look for a file of the specified name andtype
whose time field is that offile-clock
. This permits us to have a ``probe-file'' aspect toopen-file
: one can ask for a file, find it does not exist, but come back later and find that it does now exist.
Written-files
, an alist whose keys have the form(string type time1 time2)
, wherestring
is a file name,type
is one of:character
,:byte
or:object
, andtime1
andtime2
are integers.Time1
andtime2
correspond to thefile-clock
time at which the channel for the file was opened and closed. This field is write-only; the only operation that affects this field isclose-output-channel
, whichcons
es a new entry on the front.
Read-files
, a list of the form(string type time1 time2)
, wherestring
is a file name andtime1
andtime2
were the times at which the file was opened for reading and closed. This field is write only.
Writeable-files
, an alist whose keys have the form(string type time)
. To open a file for output, we require that the name, type, and time be on this list.
List-all-package-names-lst
, a list oftrue-listps
. Roughly speaking, thecar
of this list is the list of all package names known to this Common Lisp right now and thecdr
of this list is the value of thisstate
variable after you look at itscar
. The function,list-all-package-names
, which takes the state as an argument, returns thecar
andcdr
s the list (returning a new state too). This essentially gives ACL2 access to what is provided by CLTL'slist-all-packages
.Defpkg
uses this feature to insure that the about-to-be-created package is new in this lisp. Thus, for example, inakcl
it is impossible to create the package"COMPILER"
withdefpkg
because it is on the list, while in Lucid that package name is not initially on the list.
We recommend avoiding the use of the state object when writing ACL2 code intended to be used as a formal model of some system, for several reasons. First, the state object is complicated and contains many components that are oriented toward implementation and are likely to be irrelevant to the model in question. Second, there is currently not much support for reasoning about ACL2 functions that manipulate the state object. Third, the documentation about state is not as complete as one might wish for serious programming that uses state.
If a user is building a model that includes a system state, it is
better to represent that state explicitly in the model rather than
use the ACL2 state object. ACL2 functions that manipulate
association lists (for example, see assoc) can be used in place
of @
and assign
to access and update the state component of
the model. As of this writing, the "books"
directory of the
ACL2 distribution contains a number of theorems already proved about
such functions.
A consequence of this recommendation is that ACL2 constructs like
pprogn
and er-progn
that depend on the state object will not
appear in user-built models.
why we restrict encapsulated recursive functions
Parent topic: MISCELLANEOUS Home
It is illegal for one of the functions introduced in the signature
of an encapsulate
event to be involved in the induction scheme
suggested by a non-local
ly defined recursive function in that
encapsulate
. Such recursive functions can give rise to so-called
``subversive'' induction schemes in the sense that when used outside
the encapsulate
the scheme is unsound. Normally, the induction
suggested by a function is justified by the termination proof for
the recursion. But in the case of recursive functions defined
within encapsulations, the termination proof is constructed in a
context in which the functions are to be viewed as ``constrained.''
The termination proof might therefore take advantage of properties
of the witnesses that are not exported from the encapsulate
.
Hence, crucial restrictions on the constrained functions may be
lost. We give examples and advice below.
The following (illegal) event illustrates the problem posed by subversive inductions.
(encapsulate ((test (x) t) (d (x) t) (p (x) t))Consider just the first three events above: the local definitions of(local (defun test (x) (consp x)))
(local (defun d (x) (declare (xargs :mode :logic)) (cdr x)))
(defun foo (x) (declare (xargs :mode :logic)) (if (test x) (foo (d x)) t))
(local (defun p (x) (declare (ignore x)) t))
(defthm base-case (implies (not (test x)) (p x)))
(defthm induction-step (implies (and (test x) (p (d x))) (p x))))
test
and d
and the non-local recursive function foo
. Test
and d
will be constrained to have certain properties, but within the
encapsulate
they are locally witnessed by consp
and cdr
,
respectively. Observe that foo
recurses by stepping from x
to (d x)
when (test x)
is true. This recursion terminates because of
properties of the witnesses for test
and d
. These properties need
not be exported from the encapsulation. But when foo
is exported
from the encapsulation, it suggests that it is permissible to
recur/induct by stepping ``down'' to (d x)
from x
when (test x)
is
true. Since we do not constrain (d x)
to be smaller than x
when
(test x)
the induction suggestion by foo
can be used to prove
non-theorems. Because foo
uses test
and d
in its recursion we say
the induction suggested by foo
is ``subversive'' outside the
encapsulation and do not permit such a foo
to be defined
non-locally. That is, the attempt to submit the encapsulate event
above will cause an error and complain about foo
.
We give some advice below on how to carry out such encapsulations as
might have been intended by that above. But first we show how an
inconsistency can be deduced when the above encapsulate
is
permitted.
Note that the encapsulate
introduced one other function symbol, p
.
Furthermore, the only constraints on test
, d
, and p
are the last
two theorems above. These theorems are the base case and induction
step for a proof of (p x)
based on the induction suggested by foo
.
We arrange for these theorems to be provable simply by defining p
locally to be t
.
Once outside the encapsulate we can prove (p x)
by the spurious
induction suggested by foo
.
(defthm p-true (p x) :hints (("Goal" :induct (foo x))))The proof is immediate given the two theorems exported from the
encapsulate
.
But then we can functionally instantiate p
in the theorem above to
be nil
! We must, of course, satisfy the constraints on p
, namely
the two theorems about test
, d
, and p
exported from the encapsulate
.
We can satisfy the constraints by choosing test
to be always t
.
(defthm subversion! nil :hints (("Goal" :use (:functional-instance p-true (test (lambda (x) t)) (p (lambda (x) nil))))) :rule-classes nil)This concludes our illustration of what can go wrong were we to permit recursive functions in encapsulations to use the constrained functions in their recursions. We now move on to our advice about how to achieve the (non-nefarious) ends intended by the disallowed encapsulations.
One often desires to define recursive functions such as foo
, i.e.,
functions that use constrained functions in their recursion.
However, one should not try to introduce such functions within the
same encapsulation as the constrained functions. Instead, introduce
the constrained functions, including among their constraints the
measure theorems sufficient to justify the intended recursive uses,
and define the recursive functions outside the encapsulation
environment. Thus, the following pair of events achieves, perhaps,
the intended effect of the original encapsulation:
(encapsulate ((test (x) t) (d (x) t)) (local (defun test (x) (consp x))) (local (defun d (x) (declare (xargs :mode :logic)) (cdr x))) (defthm d-decreases (implies (test x) (< (acl2-count (d x)) (acl2-count x)))))Another alternative, depending on one's original intentions, is to include the definition of(defun foo (x) (if (test x) (foo (d x)) t))
foo
in the encapsulation but to make it
local. This allows foo
to be used in other local events of that
encapsulation but does not export it and its subversive induction
scheme.
Finally, it may be that foo
is needed outside the encapsulation
environment but the user does not intend for foo
to suggest any
induction schemes. If this is the case, one should include foo
in the signature of the encapsulate (and so make its definition
local) and prove a non-local :
definition
rule which states the
recurrence equation for foo
without requiring a termination
argument. The :clique
and :controller-alist
fields for the
:
definition
rule should be those for the recursive definition of
foo
. See definition.
Note that the functions introduced in the signature may not even
occur ancestrally in the induction scheme suggested by a
non-local
ly defined recursive function in the encapsulate
.
That is, they may not occur in definitions of, or constraints on,
functions that occur in such induction schemes; and those
functions may not occur in such induction schemes; and so on.
the syntax of ACL2 is that of Common Lisp
Parent topic: MISCELLANEOUS Home
For the details of ACL2 syntax, see CLTL. For examples of ACL2
syntax, use :
pe
to print some of the ACL2 system code. For example:
:pe assoc-equal :pe dumb-occur :pe fn-var-count :pe add-linear-variable-to-alist
There is no comprehensive description of the ACL2 syntax yet, except that found in CLTL. Also see term.
to attach a heuristic filter on a :
rewrite
rule
Parent topic: MISCELLANEOUS Home
Example: Consider the :REWRITE rule created fromThe(IMPLIES (SYNTAXP (NOT (AND (CONSP X) (EQ (CAR X) 'NORM)))) (EQUAL (LXD X) (LXD (NORM X)))).
syntaxp
hypothesis in this rule will allow the rule to be
applied to (lxd (trn a b))
but will not allow it to be applied to
(lxd (norm a))
.
General Form: (SYNTAXP test)may be used as the nth hypothesis in a
:
rewrite
rule whose
:
corollary
is (implies (and hyp1 ... hypn ... hypk) (equiv lhs rhs))
provided test
is a term, test
contains at least one variable, and
every variable occuring freely in test
occurs freely in lhs
or in
some hypi
, i<n
. Formally, syntaxp
is a function of one argument;
syntaxp
always returns t
and so may be added as a vacuous
hypothesis. However, the test ``inside'' the syntaxp
form is
actually treated as a meta-level proposition about the proposed
instantiation of the rule's variables and that proposition must
evaluate to true (non-nil
) to ``establish'' the syntaxp
hypothesis.
We illustrate this by slightly elaborating the example given above.
Consider a :
rewrite
rule whose :
corollary
is:
(IMPLIES (AND (RATIONALP X) (SYNTAXP (NOT (AND (CONSP X) (EQ (CAR X) 'NORM))))) (EQUAL (LXD X) (LXD (NORM X))))How is this rule applied to
(lxd (trn a b))
? First, we form a
substitution that instantiates the left-hand side of the conclusion
of the rule so that it is identical to the target term. In the
present case, the substitution replaces x
with (trn a b)
. Then we
backchain to establish the hypotheses, in order. Ordinarily this
means that we instantiate each hypothesis with our substitution and
then attempt to rewrite the resulting instance to true. Of course,
most users are aware of some exceptions to this general rule. For
example, if a hypothesis contains a ``free-variable'' -- one not
bound by the current substitution -- we attempt to extend the
substitution by searching for an instance of the hypothesis among
known truths. Force
d hypotheses are another exception to the
general rule of how hypotheses are relieved. Hypotheses marked with
syntaxp
, as in (syntaxp test)
, are also exceptions. Instead of
instantiating the hypothesis and trying to establish it, we evaluate
test
in an environment in which its variable symbols are bound to
the quotations of the terms to which those variables are bound in
the instantiating substitution. In the case in point, we evaluate
the test
(NOT (AND (CONSP X) (EQ (CAR X) 'NORM)))in an environment where
x
is bound to '(trn a b)
, i.e., the list
of length three whose car
is the symbol 'trn
. Thus, the test
returns t
because x
is a consp
and its car
is not the symbol 'norm
.
When the syntaxp
test evaluates to t
, we consider the syntaxp
hypothesis to have been established; this is sound because
(syntaxp test)
is t
regardless of test
. If the test
evaluates to nil
(or fails to evaluate because of guard violations)
we act as though we cannot establish the hypothesis and abandon the
attempt to apply the rule; it is always sound to give up.
Note that the test of a syntaxp
hypothesis does not deal with the
meaning or semantics or values of the terms but with their syntactic
forms. In the example above, the syntaxp
hypothesis allows the rule
to be applied to every target of the form (lxd u)
, provided
(rationalp u)
can be established and u
is not of the form (norm v)
.
Observe that without this syntactic restriction the rule above could
loop producing a sequence of increasingly complex targets (lxd a)
,
(lxd (norm a))
, (lxd (norm (norm a)))
, etc. An intuitive reading of
the rule might be ``norm
the argument of lxd
(when it is rationalp
)
unless it has already been norm
ed.''
Another common syntactic restriction is
(SYNTAXP (AND (CONSP X) (EQ (CAR X) 'QUOTE))).A rule with such a hypothesis can be applied only if
x
is bound to
a specific constant. Thus, if x
is 23
(which is actually
represented internally as (quote 23)
), the test evaluates to t
; but
if x
is (+ 11 12)
it evaluates to nil
(because (car x)
is the symbol
'
+
). It is often desirable to delay the application of a rule until
certain subterms are in some kind of normal form so that the
application doesn't produce excessive case splits.