【问题标题】:How to avoid eval in defmacro?如何避免defmacro中的eval?
【发布时间】:2019-04-12 12:02:54
【问题描述】:

我编写了一个宏,它接受要调用的 lambda 列表并生成一个函数。 lambda 总是在 defun 参数列表中计算,但不在 defmacro 中。如何避免在defmacro 中调用eval

此代码有效:

(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 而不是传递的函数名称参数(应该被引用)。

是否可以编写 defactordefun 版本不同的传递函数名称的可能性,并且在 macro 版本中没有 eval

【问题讨论】:

  • 你可以用(list ,@fns)替换循环。
  • @jkiiski,不幸的是这不起作用:; The LET binding spec (MAPCAR ; #'(LAMBDA (X) ; (FORMAT T "Actor (type ~a): [~a]~&amp;" (TYPE-OF X) X)) ; #:G647) is malformed.
  • 听起来您缺少 let 绑定的右括号,因此它也将以下 MAPCAR 表单作为绑定。
  • 注释掉format后,同样的错误:; The LET binding spec (DEFUN INVERT-CASE (IN OUT &amp;OPTIONAL (POS 0)) ... is malformed.
  • 它有什么作用?这个简单的例子看起来非常复杂。

标签: macros common-lisp


【解决方案1】:

第一个 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)))))

【讨论】:

    【解决方案2】:

    这通常不需要是原样的宏。您主要可以使用辅助函数:

    (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))))
    

    【讨论】:

    • Dan Robertson,你的观察确实很有趣。现在我将使用第一个解决方案,因为process-line 将与不同宏创建的演员一起工作,而不仅仅是defactor(我在deftransformer 中重命名)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-28
    相关资源
    最近更新 更多