Recursion 如何在公共Lisp宏中管理递归

Recursion 如何在公共Lisp宏中管理递归,recursion,macros,lisp,common-lisp,Recursion,Macros,Lisp,Common Lisp,我需要编写一个通用的Lisp宏,它包含一个符号和一个列表。该列表由两个元素的列表组成:一个符号和一个字符串,如下所示: ((X "foo") (Y "bar") (Z "qwerty")) 宏递归工作,它搜索列表中的符号,如果找到symbol则返回T,否则返回NIL。我写这段代码: (defmacro already-exist (symbol my-list) (cond ((null (eval my-list)) NIL) ((eql (caar (eval my-l

我需要编写一个通用的Lisp宏,它包含一个符号和一个列表。该列表由两个元素的列表组成:一个符号和一个字符串,如下所示:

((X "foo") (Y "bar") (Z "qwerty"))
宏递归工作,它搜索列表中的符号,如果找到
symbol
则返回
T
,否则返回
NIL
。我写这段代码:

(defmacro already-exist (symbol my-list)
  (cond ((null (eval my-list)) NIL)
        ((eql (caar (eval my-list)) symbol)
     T)
    (T `(already-exist symbol ,(cdr (eval my-list))))))
但问题在于递归部分。事实上,如果我试图使用第一个元素中没有
symbol
的列表运行宏,我会得到一个错误。例如:

(defparameter listt '((X "foo") (Y "bar") (Z "qwerty")))

(already-exist Y listt)
我得到的错误是“非法函数调用”。我认为这是因为宏试图将
Y
作为函数调用进行求值。我怎样才能解决这个问题?一般来说,用公共Lisp编写递归宏的最佳方法是什么

我需要编写一个公共Lisp宏[…]该宏以递归方式工作

您实际上并不需要宏,事实上,如果您的值仅在运行时已知,则宏无法解决此问题。如果希望不计算符号,则需要引用符号

<代码>(评估我的列表)

从宏调用eval是一个很大的代码气味。您的宏正在处理代码,
列表
符号在这一点上没有任何意义,只是它是一个符号。宏很可能不会在
list
绑定到有意义值的环境中展开(此外,
eval
在空词汇环境中工作)

可以进行递归扩展,但宏本身不是递归的:

* (defmacro foo (x) (foo x))
; in: DEFMACRO FOO
;     (FOO X)
; 
; caught STYLE-WARNING:
;   undefined function: FOO
; 
; compilation unit finished
;   Undefined function:
;     FOO
;   caught 1 STYLE-WARNING condition
STYLE-WARNING:
   FOO is being redefined as a macro when it was previously assumed to be a function.
* (foo 3)

debugger invoked on a UNDEFINED-FUNCTION in thread
#<THREAD "main thread" RUNNING {100399C503}>:
  The function COMMON-LISP-USER::FOO is undefined.

请参阅。

实际错误是什么?

实际错误如下:

CL-USER 3 > (already-exist Y listt)

Error: Illegal car (Y "bar") in compound form ((Y "bar") (Z "qwerty")).
因此,您正在尝试执行此表达式,它不是有效的Lisp代码:

((Y“bar”)(Z“qwerty”)

错误1:

您的代码为您计算的
我的列表
获取一个符号。下次它得到一个列表时,您不应该评估它

错误2:

使用
symbol
而不是
symbol
的值创建表单

尝试修复导致丑陋解决方案的问题

CL-USER 10 > (member 'y listt :key #'car)
((Y "bar") (Z "qwerty"))
  • 当列表中有一个符号作为值时,尝试仅对其求值
  • 通过在符号前面加逗号来传递符号的值
例如:

CL-USER 2 > (defmacro already-exist (symbol my-list)                            
              (when (symbolp my-list)                                           
                (setf my-list (eval my-list)))                                  
              (cond ((null my-list)                                             
                     NIL)                                                       
                    ((eql (caar my-list) symbol)                                
                     T)                                                         
                    (T `(already-exist ,symbol ,(cdr my-list)))))
ALREADY-EXIST

CL-USER 3 > (defparameter listt '((X "foo") (Y "bar") (Z "qwerty")))
LISTT

CL-USER 4 > (already-exist Y listt)
T

CL-USER 5 > (already-exist A listt)
NIL
但这毫无意义,并带来了新的问题

编写调用
eval
的递归宏调用是一种糟糕的风格
eval
无法获取词法绑定的值,因为计算不会在词法环境中进行

使用功能

CL-USER 10 > (member 'y listt :key #'car)
((Y "bar") (Z "qwerty"))
这意味着是真的。

递归宏是不可能的 想象一下你试过:

(defmacro expand (&rest elements)
  (if (not (null (cdr elements)))
      `(+ ,(car elements) ,(expand (cdr elements)))
      (car elements)))
现在,在编译宏函数时,它会展开所有宏,以便在其中调用
(展开(cdr元素))

(defmacro expand (&rest elements)
  (if (not (null (cdr elements)))
      `(+ ,(car elements) (+ (cdr elements) (+ (cdr elements) (+ (cdr elements) (+ (cdr elements) ...))))
      (car elements)))
你看到了吗?现在,假设您只是展开第一部分,而不是递归,而是使用
expand
保留一个更简单的表达式:

(defmacro expand (&rest elements)
  (if (not (null (cdr elements)))
      `(+ ,(car elements) (expand ,@(cdr elements)))
      (car elements)))
这是完全不同的,因为宏从不直接使用宏。但是
(展开1 2 3)
展开为
(+1(展开2 3))
,lisp继续展开宏,直到没有剩下宏为止,使
(+1(+2 3))
没有递归

CL-USER 10 > (member 'y listt :key #'car)
((Y "bar") (Z "qwerty"))
宏中的递归函数正常: 它也不需要是本地定义的函数。通常,我将大部分功能实现为一个函数,并生成一个宏来延迟对某些参数的计算,使宏只调用函数:

(defun make-env-fun (names)
  (mapcar (lambda (name) (cons name (symbol-function name))) names))

(defmacro make-env (&rest variables)
  `(make-env-fun ',variables))

(make-env cons car cdr)
; ==> ((cons . #<system-function cons>) 
;      (car . #<system-function car>)
;      (cdr . #<system-function cdr>))
 
(定义使环境变得有趣(名称)
(mapcar(lambda(名称)(cons名称(符号功能名称)))名称)
(defmacro生成环境(&rest变量)
`(使环境变得有趣(变量))
(制造环境控制系统车辆cdr)
; ==> ((反对意见)
(汽车)
(cdr.#)
因此宏存在,因为我不想执行
(make env'cons'car'cdr)
(make env'(cons car'cdr))
。宏仅修复该问题,而不是函数仍在执行的实际工作


因此,为了解决您的问题,您需要一个允许
(已存在符号((符号“bla”))
的宏,而不是
(已存在有趣的“符号”((符号“bla”)))
。您看到了吗?

您应该为此使用一个函数。宏用于生成代码。您也可以使用
ASSOC
(或
(成员…:key#'first)
)查看该键是否已存在。@jkiiski我需要将一个不带引号的符号传递给宏,并且我不希望代码计算该符号,因此我选择编写一个宏,而不是函数。在函数中是否有任何方式可以获得相同的行为?如果你知道,请告诉我。无论如何,我不知道ASSOC,所以谢谢!我会记住的!您应该只引用符号或使用关键字。搞乱一个看起来像函数的表单的计算会让阅读代码的人感到困惑。除非你在宏扩展时知道这个列表,否则你不能用宏来做这件事。您可以通过将宏扩展为函数来实现这一点。然而,我在这里支持jkiiski:只需使用
引用
。你的意思是
,(展开(cdr元素))
还是
(展开,(cdr元素))
?就我所知,前者没有很好的定义;在SBCL上,首先假定扩展调用是宏主体中的函数。然后该名称被绑定为一个宏(它会给出一个警告)。如果我们重新定义宏,现在它将展开,但结果窗体将重新定义宏。虽然可能存在不动点,但通常“递归宏”是扩展为调用自身的宏。@coredump第一个代码块故意无效,因为它一开始就没有意义。任何解决方案都可能超越CLH,并且可能是ok,就像变异文字一样。递归宏是不可能的。扩展为使用相同宏的代码的宏并不是真正的递归,因为它只执行一个步骤