如何在Common Lisp宏中管理递归

时间:2017-08-25 12:57:08

标签: recursion macros lisp common-lisp

我需要编写一个带有符号和列表的Common 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评估为函数调用。我怎么能解决这个问题?通常,在Common Lisp中编写递归宏的最佳方法是什么?

3 个答案:

答案 0 :(得分:4)

  

我需要编写一个Common Lisp宏[...]宏以递归方式工作。

你并不真正需要一个宏,实际上如果你的值只在运行时知道,宏就无法解决问题。如果你希望它们不被评估,你需要引用符号。

  

... <nav class="navbar navbar-fixed-top" style="margin-top: 2vw; margin-right: 3vw; position: fixed;"> <div class="container-fluid"> <ul class="nav navbar-nav navbar-right"> <li><a href="#" style="border-style: solid; border-width: 1px;"><span class="glyphicon glyphicon-search rt"></span> Search</a></li> <li><a class="qt" href="#"><span class="strike"><span class="glyphicon glyphicon-minus"></span> About Us</span></a></li> <li><a href="#"><span class="strike"><span class="glyphicon glyphicon-minus"></span> Education</span></a></li> <li><a href="#"><span class="strike"><span class="glyphicon glyphicon-minus"></span> Consumer Service</span></a></li> <li><a href="#"><span class="glyphicon glyphicon-menu-hamburger" style="border-radius: 50%; border-width: 1px; border-style: solid; padding: 5px; margin-top: -5px;"></span></a></li> </ul> </div> </nav> <div id="test" class="jumbotron" style:"padding-top:30px"> <h1>Twitter Bootstrap 3.0</h1> <p class="lead">Starter template with CSS and JS included.</p> <p><a class="btn btn-lg btn-primary" href="#fork">Fork this fiddle</a></p> </div> ...

从宏调用eval是一个很大的代码味道。你的宏正在处理代码,@import url('//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-theme.min.css'); body { margin: 10px; } #test { margin-top: 87px; height: 2000px; } 符号此处没有任何意义,除了它是一个符号。在(eval my-list)绑定到有意义值的环境中,宏很可能 not 将被扩展(此外,list在null词法环境中工作)。

可以使用递归扩展,但宏本身不是递归的:

list

Macroexpansion以固定点方式应用:从代码X0开始,将X1计算为X0的宏展开,并继续直到没有宏扩展为止。您的宏不会调用自身,宏扩展工具会在每次传递后经常调用它(这也是为什么在宏扩展期间无法建立动态绑定的原因,除非您手动调用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. 将是仅在运行时知道的值,则需要编写常规函数。基本上,你想做:

macroexpand

请参阅MEMBERMACROEXPAND

答案 1 :(得分:3)

实际错误是什么?

实际错误是:

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:

您的代码会获得您评估的my-list符号。下次获取列表时,您不应该对其进行评估。

错误2:

您使用symbol创建了一个表单,而不是symbol的值。

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

  • 尝试仅在符号为值
  • 时评估my-list
  • 通过在其前面添加逗号来传递symbol的值

示例:

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

这意味着 true

答案 2 :(得分:1)

递归宏是不可能的

想象一下,你试过了:

(defmacro expand (&rest elements)
  (if (not (null (cdr elements)))
      `(+ ,(car elements) ,(expand (cdr elements)))
      (car elements)))

现在编译宏函数时,它会展开所有宏,以便在其中调用(expand (cdr elements)) ..

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

这完全不同,因为宏从不直接使用宏。但是,(expand 1 2 3)会扩展为(+ 1 (expand 2 3)),并且lisp会继续扩展宏,直到没有任何内容为止,只留下(+ 1 (+ 2 3)) 没有递归

递归函数在宏中是可以的:

(defmacro expand (&rest elements)
  (labels ((recfun (elements)
             (if (not (null (cdr elements)))
                 `(+ ,(car elements) ,(recfun (cdr elements)))
                 (car elements))))
    (recfun elements)))

它也不需要是本地定义的函数。通常我将大部分功能作为一个函数实现,并制作一个宏来延迟评估一些参数,让宏只调用函数:

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

所以宏存在,因为我不想做(make-env 'cons 'car 'cdr)(make-env '(cons car cdr))。该宏仅修复了该问题,而不是该函数仍在进行的实际工作。

因此,为了解决您的问题,您需要一个允许(already-exist symbol ((symbol "bla")))代替(already-exist-fun 'symbol '((symbol "bla")))的宏。你看到了吗?