【发布时间】: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 而不是传递的函数名称参数(应该被引用)。
是否可以编写 defactor 与 defun 版本不同的传递函数名称的可能性,并且在 macro 版本中没有 eval?
【问题讨论】:
-
你可以用
(list ,@fns)替换循环。 -
@jkiiski,不幸的是这不起作用:
; The LET binding spec (MAPCAR ; #'(LAMBDA (X) ; (FORMAT T "Actor (type ~a): [~a]~&" (TYPE-OF X) X)) ; #:G647) is malformed. -
听起来您缺少 let 绑定的右括号,因此它也将以下
MAPCAR表单作为绑定。 -
注释掉
format后,同样的错误:; The LET binding spec (DEFUN INVERT-CASE (IN OUT &OPTIONAL (POS 0)) ... is malformed. -
它有什么作用?这个简单的例子看起来非常复杂。
标签: macros common-lisp