如何在defmacro中避免评估?

时间:2019-04-12 12:02:55

标签: 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)))))

此代码的评估符合预期:

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

其用法是:

;; 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

在没有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))))]

其余所有显然都行不通。

如果我将defmacro转换为defun,则不需要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)))

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

是否可以编写defactor并通过传递与defun版本不同的函数名,而在eval版本中不传递macro的可能性?

2 个答案:

答案 0 :(得分: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)))))

答案 1 :(得分:1)

通常不需要原样是宏。您通常可以使用辅助功能:

(defun make-actor (&rest funs)
  (lambda (in out &optional (pos 0)
    (loop for actor in funs
      when (funcall actor in out pos) return it)))

并编写一个简单的宏:

(defmacro defactor (name &rest funs)
  `(let ((f (make-actor ,@funs)))
      (defun ,name (in out &optional (pos 0)) (funcall f in out pos))))

但是,这在表达性(实际上将宏称为函数)或效率(编译器必须非常聪明才能通过倾斜一堆复杂的事情来改进代码)方面并没有多大益处


这是实现此类目标的另一种方式:

(defmacro defactor (name (in out pos) &rest actors)
  (let ((inv (gensym "IN"))
        (outv (gensym "OUT"))
        (posv (gensym "POS")))
    `(defun ,name (,inv ,outv &optional (,posv 0))
        ;; TODO: (declare (type ...) ...)
        (or ,@(loop for form in actors 
                 collect `(let ((,in ,inv) (,out ,outv) (,pos ,posv)) ,form)))))

然后像这样使用它:

(defactor invert-case (in out pos)
  (let ((ch (char str pos)))
    (when (upper-case-p ch)
      (format out "~a" (char-downcase ch))
      (1+ pos)))
  (let ((ch (char str pos)))
    (when (lower-case-p ch)
      (format out "~a" (char-upcase ch))
      (1+ pos))))
相关问题