Major Section: EVENTS
Suppose you define a function (fn x) with a guard of
(good-input-p x), and you know that when the guard holds, the measure
decreases on each recursive call. Unfortunately, the definitional principle
(see defun) ignores the guard. For example, if the definition has the form
(defun fn (x)
(declare (xargs :guard (good-input-p x)))
(if (not-done-yet x)
(... (fn (destr x)) ...)
...))
then in order to admit this definition, ACL2 must prove the appropriate
formula asserting that (destr x) is ``smaller than'' x under the
assumption (not-done-yet x) but without the assumption
(good-input-p x), even if (not-done-yet x) is true. In essence, it
may be necessary to submit instead the following definition.
(defun fn (x)
(declare (xargs :guard (good-input-p x)))
(if (good-input-p x)
(if (not-done-yet x)
(... (fn (destr x)) ...)
...)
nil)
But it is unfortunate that when calls of fn are evaluated, for example
when fn is applied to an explicit constant during a proof, then a call of
good-input-p must now be evaluated on each recursive call.
Fortunately, defexec provides a way to keep the execution efficient. For
the example above we could use the following form.
(defexec fn (x)
(declare (xargs :guard (good-input-p x)))
(mbe :logic (if (good-input-p x)
(if (not-done-yet x)
(... (fn (destr x)) ...)
...)
nil)
:exec (if (not-done-yet x)
(... (fn (destr x)) ...)
...)))
Here ``mbe'' stands for ``must-be-equal'' and, roughly speaking, its
call above is logically equal to the :logic form but is evaluated using
the :exec form when the guard holds. See mbe. The effect is thus to
define fn as shown in the defun form above, but to cause execution
of fn using the :exec body. The use of defexec instead of
defun in the example above causes a termination proof to be performed,
in order to guarantee that evaluation always theoretically terminates, even
when using the :exec form for evaluation.
Example:The above example macroexpands to the following.; Some of the keyword arguments in the declarations below are irrelevant or ; unnecessary, but they serve to illustrate their use.
(defexec f (x) (declare (xargs :measure (+ 15 (acl2-count x)) :ruler-extenders :basic :hints (("Goal" :in-theory (disable nth))) :guard-hints (("Goal" :in-theory (disable last))) :guard (and (integerp x) (<= 0 x) (< x 25))) (exec-xargs :test (and (integerp x) (<= 0 x)) :default-value 'undef ; defaults to nil :measure (nfix x) :ruler-extenders :basic :well-founded-relation o<)) (mbe :logic (if (zp x) 1 (* x (f (- x 1)))) :exec (if (= x 0) 1 (* x (f (- x 1))))))
(ENCAPSULATE ()
(LOCAL
(ENCAPSULATE ()
(SET-IGNORE-OK T)
(SET-IRRELEVANT-FORMALS-OK T)
(LOCAL (DEFUN F (X)
(DECLARE
(XARGS :VERIFY-GUARDS NIL
:HINTS (("Goal" :IN-THEORY (DISABLE NTH)))
:MEASURE (NFIX X)
:RULER-EXTENDERS :BASIC
:WELL-FOUNDED-RELATION O<))
(IF (AND (INTEGERP X) (<= 0 X))
(IF (= X 0) 1 (* X (F (- X 1))))
'UNDEF)))
(LOCAL (DEFTHM F-GUARD-IMPLIES-TEST
(IMPLIES (AND (INTEGERP X) (<= 0 X) (< X 25))
(AND (INTEGERP X) (<= 0 X)))
:RULE-CLASSES NIL))))
(DEFUN F (X)
(DECLARE (XARGS :MEASURE (+ 15 (ACL2-COUNT X))
:RULER-EXTENDERS :BASIC
:HINTS (("Goal" :IN-THEORY (DISABLE NTH)))
:GUARD-HINTS (("Goal" :IN-THEORY (DISABLE LAST)))
:GUARD (AND (INTEGERP X) (<= 0 X) (< X 25))))
(MBE :LOGIC
(IF (ZP X) 1 (* X (F (- X 1))))
:EXEC
(IF (= X 0) 1 (* X (F (- X 1)))))))
Notice that in the example above, the :hints in the local
definition of F are inherited from the :hints in the xargs of
the defexec form. We discuss such inheritance below.
CAVEAT: Termination is not considered for calls of mbe under the
top-level call. Moreover, the :exec part of an mbe call under the
:logic part of any superior mbe call is completely ignored.
General Form:
(defexec fn (var1 ... varn) doc-string dcl ... dcl
(mbe :LOGIC logic-body
:EXEC exec-body))
where the syntax is identical to the syntax of defun where the body is
a call of mbe, with the exceptions described below. Thus, fn is the
symbol you wish to define and is a new symbolic name and (var1 ... varn)
is its list of formal parameters (see name). The first exception is that at
least one dcl (i.e., declare form) must specify a :guard,
guard. The second exception is that one of the dcls is allowed to
contain an element of the form (exec-xargs ...). The exec-xargs
form, if present, must specify a non-empty keyword-value-listp each of
whose keys is one of :test, :default-value, or one of the standard
xargs keys of :measure, :ruler-extenders,
:well-founded-relation, :hints, or :stobjs. Any of these five
standard xargs keys that is present in an xargs of some dcl but
is not specified in the (possibly nonexistent) exec-xargs form is
considered to be specified in the exec-xargs form, as illustrated in the
example above for :hints. (So for example, if you want :hints in the
final, non-local definition but not in the local definition, then specify the
:hints in the xargs but specify :hints nil in the
exec-xargs.) If :test is specified and not nil, let test be
its value; otherwise let test default to guard. If
:default-value is specified, let default-value be its value; else
default-value is nil. Default-value should have the same
signature as exec-body; otherwise the defexec form will fail to
be admitted.
The above General Form's macroexpansion is of the form
(PROGN encap final-def), where encap and final-def are as
follows. Final-def is simply the result of removing the exec-xargs
declaration (if any) from its declare form, and is the result of
evaluating the given defexec form, since encap is of the following
form.
; encap (ENCAPSULATE () (set-ignore-ok t) ; harmless for proving termination (set-irrelevant-formals-ok t) ; harmless for proving termination (local local-def) (local local-thm))The purpose of
encap is to ensure the the executable version of name
terminates on all arguments. Thus, local-def and local-thm are as
follows, where the xargs of the declare form are the result of
adding :VERIFY-GUARDS NIL to the result of removing the :test and
(optional) :default-value from the exec-xargs.
; local-def
(DEFUN fn formals
(DECLARE (XARGS :VERIFY-GUARDS NIL ...))
(IF test
exec-body
default-value))
; local-thm
(DEFTHM fn-EXEC-GUARD-HOLDS
(IMPLIES guard test)
:RULE-CLASSES NIL)
We claim that if the above local-def and local-thm are admitted, then
all evaluations of calls of fn terminate. The concern is that the use
of mbe in final-def allows for the use of exec-body for a call
of fn, as well as for subsequent recursive calls, when guard holds
and assuming that the guards have been verified for final-def. However,
by local-thm we can conclude in this case that test holds, in which
case the call of fn may be viewed as a call of the version of fn
defined in local-def. Moreover, since guards have been verified for
final-def, then guards hold for subsequent evaluation of exec-body,
and in particular for recursive calls of fn, which can thus continue to
be viewed as calls using local=def.