如何在defmacro中避免评估?

Ale*_*opa 2 macros common-lisp

我编写了一个宏,该宏接受要调用的lambda列表并生成一个函数。Lambda总是在defun参数列表中求值,而不是在中defmacro。如何避免拨打电话到eval内部defmacro

此代码有效:

(defmacro defactor (name &rest fns)
  (let ((actors (gensym)))
    `(let (;(,actors ',fns)
           (,actors (loop for actor in ',fns
                          collect (eval actor)))) ; This eval I want to avoid
       (mapcar #'(lambda (x) (format t "Actor (type ~a): [~a]~&" (type-of x) x)) ,actors)
       (defun ,name (in out &optional (pos 0))
         (assert (stringp in))
         (assert (streamp out))
         (assert (or (plusp pos) (zerop pos)))
         (loop for actor in ,actors
               when (funcall actor in out pos)
               return it)))))

;; Not-so-relevant use of defactor macros
(defactor invert-case
    #'(lambda (str out pos)
        (let ((ch (char str pos)))
          (when (upper-case-p ch)
            (format out "~a" (char-downcase ch))
            (1+ pos))))
  #'(lambda (str out pos)
      (let ((ch (char str pos)))
        (when (lower-case-p ch)
          (format out "~a" (char-upcase ch))
          (1+ pos)))))
Run Code Online (Sandbox Code Playgroud)

该代码的评估符合预期:

Actor (type FUNCTION): [#<FUNCTION (LAMBDA (STR OUT POS)) {100400221B}>]
Actor (type FUNCTION): [#<FUNCTION (LAMBDA (STR OUT POS)) {100400246B}>]
INVERT-CASE
Run Code Online (Sandbox Code Playgroud)

其用法是:

;; Complete example
(defun process-line (str &rest actors)
  (assert (stringp str))
  (with-output-to-string (out)
    (loop for pos = 0 then (if success success (1+ pos))
          for len = (length str)
          for success = (loop for actor in actors
                              for ln = len
                              for result = (if (< pos len)
                                               (funcall actor str out pos)
                                               nil)
                              when result return it)
          while (< pos len)
          unless success do (format out "~a" (char str pos)))))

(process-line "InVeRt CaSe" #'invert-case) ; evaluates to "iNvErT cAsE" as expected
Run Code Online (Sandbox Code Playgroud)

如果没有eval,则defactor上面的计算结果为:

Actor (type CONS): [#'(LAMBDA (STR OUT POS)
                        (LET ((CH (CHAR STR POS)))
                          (WHEN (UPPER-CASE-P CH)
                            (FORMAT OUT ~a (CHAR-DOWNCASE CH))
                            (1+ POS))))]
Actor (type CONS): [#'(LAMBDA (STR OUT POS)
                        (LET ((CH (CHAR STR POS)))
                          (WHEN (LOWER-CASE-P CH)
                            (FORMAT OUT ~a (CHAR-UPCASE CH))
                            (1+ POS))))]
Run Code Online (Sandbox Code Playgroud)

其他所有显然都行不通。

如果转换defmacrodefun,则不需要eval

(defun defactor (name &rest fns)
  (defun name (in out &optional (pos 0))
    (assert (stringp in))
    (assert (streamp out))
    (assert (or (plusp pos) (zerop pos)))
    (loop for actor in fns
          when (funcall actor in out pos)
          return it)))
Run Code Online (Sandbox Code Playgroud)

但是,它总是定义函数,name而不是传递的函数名参数(应加引号)。

是否可以编写defactordefun版本不同的函数名,而不必evalmacro版本中传递函数名?

650*_*502 6

首先,您使事情变得比必要的要复杂loop。。。而是收集参数

(defmacro defactor (name &rest fns)
  (let ((actors (gensym)))
    `(let ((,actors (list ,@fns)))
       (mapcar #'(lambda (x) (format t "Actor (type ~a): [~a]~&" (type-of x) x)) ,actors)
       (defun ,name (in out &optional (pos 0))
         (assert (stringp in))
         (assert (streamp out))
         (assert (or (plusp pos) (zerop pos)))
         (loop for actor in ,actors
               when (funcall actor in out pos)
               return it)))))
Run Code Online (Sandbox Code Playgroud)