Can I put condition in emacs lisp macro?

2019-07-07 01:44发布

问题:

How to achieve something like this?

(defmacro mood (x)
  (if (equal (symbol-name x) "t")
      `(defun happy ()
         (message "Happy"))
    `(defun sad ()
       (message "Sad")))
  )

My aim is to create different function base on argument. Is there any problem doing so?

回答1:

Edit 2: You're right -- for cases in which the code being evaluated at expansion-time is entirely dependent on the values of the (unevaluated) macro arguments, I believe it is safe for the macro's returned form to be generated conditionally, based upon those arguments.

You just need to be aware that any behaviour which is conditional upon dynamic values needs to be dealt with as a part of the expanded form.

(e.g. if the macro argument were a variable, and you were testing the value of the variable in your condition, it would be unsafe for that test to occur at expansion time, as that value is liable to vary between the macro's expansion time, and the time(s) that the expanded code is evaluated.)

So the specific example in your question is indeed safe as-is, and therefore my variations (below) are not actually necessary in this case. However expansion-time evaluations are certainly something you will want to be cautious about in general.

Initial answer follows...


Macros are expanded at compile time. (Or in recent versions of Emacs, should no byte-compiled version of the library be available, they will usually be compiled "eagerly" at load time).

In these scenarios, any code which is not a part of the form returned by the macro will be evaluated at most once per session, but quite likely just once ever for a given expansion of the code (whereas the expanded code might then be called numerous times).

If you need your expanded code to act conditionally at run-time, the conditions must be a part of the form returned by the macro.

Edit: For example, I imagine you actually wanted to write something more like:

(defmacro mood (x)
  `(if (equal (symbol-name ,x) "t")
       (defun happy ()
         (message "Happy"))
     (defun sad ()
       (message "Sad"))))

Although you would (almost) never want to compare symbols by comparing their symbol-name. You've already made the assumption that the macro argument will evaluate to a symbol, so just compare the symbols directly with eq:

(defmacro mood (x)
  `(if (eq ,x t)
       (defun happy ()
         (message "Happy"))
     (defun sad ()
       (message "Sad"))))

Then for example, (mood 'foo) expands to (courtesy of M-x pp-macroexpand-last-sexp):

(if
    (eq 'foo t)
    (defun happy nil
      (message "Happy"))
  (defun sad nil
    (message "Sad")))


回答2:

There is no problem defining it. You code, actually, almost works:

(defmacro mood (x)
  (if (equal x t)
      `(defun happy ()
         (message "Happy"))
    `(defun sad ()
       (message "Sad"))))

Since if is outside of back-quotes, we can examine the value of x directly. Expanding this definition with different arguments shows that different functions are defined:

> (macroexpand '(mood t))
(defalias (quote happy) (function (lambda nil (message "Happy"))))

> (macroexpand '(mood nil))
(defalias (quote sad) (function (lambda nil (message "Sad"))))


标签: macros elisp