details about equality-variants

Here we present details about equality variants, none of which is likely to be important to the majority of ACL2 users. Please see equality-variants for relevant background.

We begin by presenting events that implement the equality variants for member, as these illustrate the events introduced for all macros having equality variants. The definition of member, just below, calls the macro let-mbe, which in turn is just an abbreviation for a combination of let and mbe.

(defmacro let-mbe (bindings &key logic exec)
  `(let ,bindings
     (mbe :logic ,logic
          :exec ,exec)))
This use of let arranges that each argument of a call of member is evaluated only once.
(defmacro member (x l &key (test ''eql))
  (declare (xargs :guard (or (equal test ''eq)
                             (equal test ''eql)
                             (equal test ''equal))))
   ((equal test ''eq)
    `(let-mbe ((x ,x) (l ,l))
              :logic (member-equal x l)
              :exec  (member-eq-exec x l)))
   ((equal test ''eql)
    `(let-mbe ((x ,x) (l ,l))
              :logic (member-equal x l)
              :exec  (member-eql-exec x l)))
   (t ; (equal test 'equal)
    `(member-equal ,x ,l))))
Inspection of the definition above shows that every call of member expands to one that is logically equivalent to the corresponding call of member-equal, which is defined as follows.
(defun member-equal (x lst)
  (declare (xargs :guard (true-listp lst)))
  (cond ((endp lst) nil)
        ((equal x (car lst)) lst)
        (t (member-equal x (cdr lst)))))
The following two definitions model equality variants of member for tests eq and eql, respectively.
(defun member-eq-exec (x lst)
  (declare (xargs :guard (if (symbolp x)
                             (true-listp lst)
                           (symbol-listp lst))))
  (cond ((endp lst) nil)
        ((eq x (car lst)) lst)
        (t (member-eq-exec x (cdr lst)))))

(defun member-eql-exec (x lst)
  (declare (xargs :guard (if (eqlablep x)
                             (true-listp lst)
                           (eqlable-listp lst))))
  (cond ((endp lst) nil)
        ((eql x (car lst)) lst)
        (t (member-eql-exec x (cdr lst)))))
At this point the user can write (member x y) or (member-equal x y) to call equality variants of member with test eql or equal, respectively. We thus provide the following macro for the eq variant.
(defmacro member-eq (x lst)
  `(member ,x ,lst :test 'eq))
Guard proof obligations generated by calls of member will include those based on its use of mbe, and are supported by the following two lemmas.
(defthm member-eq-exec-is-member-equal
  (equal (member-eq-exec x l)
         (member-equal x l)))

(defthm member-eql-exec-is-member-equal
  (equal (member-eql-exec x l)
         (member-equal x l)))
Finally, the following two events arrange that in certain contexts such as theories (including the use of in-theory in events and hints), member-eq and member are treated as references to member-equal.
(add-macro-alias member-eq member-equal)
(add-macro-alias member member-equal)

We conclude this topic by exploring the following recommendation made in the documentation for equality-variants.

For efficiency we recommend using the -equal equality variant, for example member-equal or ... :TEST 'equal, in certain contexts: defmacro, defpkg, defconst, and value-triple forms.

ACL2 reliies on the underlying Common Lisp for evaluation. It also processes events in the ACL2 logic. In order to guarantee consistency of its logical and Common Lisp evaluations, ACL2 uses a ``safe mode'' to avoid ill-guarded calls. In particular, consider the use of mbe in execution of a call of an equality variant of a primitive, F, other than its F-equal variant. The mbe call discussed above requires a connection to be established between the :logic and :exec forms. For example, if F is called with :TEST 'eql (either explicitly or as the default), then ACL2 will call both F-eql-exec and F-equal, and check that the two results are equal.

The following partial log illustrates the point above. We define a macro that calls member, and when a call of this macro is expanded during processing of a subsequent definition, we see that two membership functions are called on the same arguments.

ACL2 !>(defmacro mac (lst)
         (list 'quote (and (true-listp lst)
                           (member 'c lst :test 'eq))))

Form:  ( DEFMACRO MAC ...)
Rules: NIL
Time:  0.01 seconds (prove: 0.00, print: 0.00, other: 0.01)
ACL2 !>(trace$ member-equal member-eq-exec)
ACL2 !>(defun f () (mac (a b c d)))
1> (ACL2_*1*_ACL2::MEMBER-EQ-EXEC C (A B C D))
<1 (ACL2_*1*_ACL2::MEMBER-EQ-EXEC (C D))
1> (ACL2_*1*_ACL2::MEMBER-EQUAL C (A B C D))
<1 (ACL2_*1*_ACL2::MEMBER-EQUAL (C D))

Since F is non-recursive, its admission is trivial.

If performance is an issue then we can avoid such a problem, for example as follows. In a fresh session, let us define a suitable wrapper for calling member with :TEST 'eq. This time, the trace in our partial log shows that we have avoided calling two membership functions.

ACL2 !>(defun mem-eq (x lst)
         (declare (xargs :guard (if (symbolp x)
                                    (true-listp lst)
                                  (symbol-listp lst))))
         (member x lst :test 'eq))
[[ ... output omitted here ... ]]
ACL2 !>(defmacro mac (lst)
         (list 'quote (and (true-listp lst)
                           (mem-eq 'c lst))))

Form:  ( DEFMACRO MAC ...)
Rules: NIL
Time:  0.00 seconds (prove: 0.00, print: 0.00, other: 0.00)
ACL2 !>(trace$ member-equal member-eq-exec mem-eq)
ACL2 !>(defun f () (mac (a b c d)))
1> (ACL2_*1*_ACL2::MEM-EQ C (A B C D))
  2> (MEM-EQ C (A B C D))
  <2 (MEM-EQ (C D))
<1 (ACL2_*1*_ACL2::MEM-EQ (C D))

Since F is non-recursive, its admission is trivial.