处理参数传递给Common Lisp中的defmacro

时间:2017-10-06 00:05:49

标签: lambda macros common-lisp

在尝试解决Project Euler中的问题时,我编写了以下函数和宏:

(defun digits (n &key (base 10)) ;; Returns a list with the digits of 'n'
   (if (< n base) (list n)       ;; in a given base.
      (multiple-value-bind (div rem)
         (floor n base)
         (concatenate 'list (digits div :base base) (list rem)))))

(defmacro test-palindromes (n1 n2)
   (let* ((dn1 (digits n1)) (dn2 (digits n2))
          (hash (loop for i in dn1 ; A-list describing digit associations
                      collecting (assoc i (pairlis dn2
                            (loop for i from 0 below (length dn1)
                                  collecting i))))))
      `(lambda (n1 n2) (and ,@(loop for i in hash
                                    for j from 0
                                    collecting `(char= (char n1 ,(cdr i)) (char n2 ,j)))))))

我想要做的是生成一个lambda,如果一个字符串对应另一个字符串的特定排列,则返回T。例如:

1 > (pprint (macroexpand-1 '(test-palindromes 1089 9801)))

(LAMBDA (N1 N2)
  (AND (CHAR= (CHAR N1 3) (CHAR N2 0))
       (CHAR= (CHAR N1 2) (CHAR N2 1))
       (CHAR= (CHAR N1 1) (CHAR N2 2))
       (CHAR= (CHAR N1 0) (CHAR N2 3))))
1 >

当输入为整数时,它工作正常......

1 > (funcall (test-palindromes 1089 9801) "ALAS" "SALA")
T
1 > (funcall (test-palindromes 1089 9801) "ALAS" "SALE")
Nil

...但如果我尝试给它更复杂的输入,则会失败:

1 > (setf g 10)
10
1 > (funcall (test-palindromes 1089 (+ 9791 g)) "ALAS" "SALA")
> Error: The value (+ 9791 G) is not of the expected type REAL.
> While executing: CCL::<-2, in process listener(1).
> Type :POP to abort, :R for a list of available restarts.
> Type :? for other options.
2 >

在绝望中,通过将dn1dn2设置为(eval (digits dn1))(eval (digits dn2)),尝试了一种笨拙的解决方案。这产生了部分改善......

2 > (funcall (test-palindromes 1089 (+ 9791 g)) "ALAS" "SALA")
T

...但是这段代码仍然会失败:

(loop for pos from 0 to 66
      nconcing (loop for i in (nthcdr (1+ pos) 4-digits)
                     for j from 0
                     when (equal (sort (digits (nth pos 4-digits)) #'<)
                                 (sort (digits i) #'<))
                       collect (test-palindromes (nth pos 4-digits) i)))
> Error: Unbound variable: POS
> While executing: CCL::CHEAP-EVAL-IN-ENVIRONMENT, in process listener(1).
> Type :GO to continue, :POP to abort, :R for a list of available restarts.
> If continued: Retry getting the value of POS.
> Type :? for other options.
3 >

(变量4-digits包含4位数字的所有正方形的有序列表。)

我想我应该跳过应该eval编辑的内容,但我并不真正理解循环中发生了什么。为什么pos不再被识别?这个循环可以工作吗?任何意见都表示赞赏。

谢谢,  圣保罗

2 个答案:

答案 0 :(得分:5)

在REPL或文件中解释或编译代码时,将执行defmacro正文中的代码。在test-palindromes的最后一个循环中,您为宏提供了参数(nth pos 4-digits) - 但pos只有在loop 被执行时才有明智的含义

因此,您尝试做的事情无法通过宏完成,因为宏的输出取决于执行时输入的值

但你可以使用一个函数:

* (defun test-palindromes (n1 n2)
    (let* ((dn1 (digits (eval n1))) (dn2 (digits (eval n2)))
           (hash (loop for i in dn1
                       collecting (assoc i (pairlis dn2
                                                    (loop for i from 0 below (length dn1)
                                                          collecting i))))))
      (lambda (n1 n2)
        (loop for i in hash
              for j from 0
              always (char= (char n1 (cdr i)) (char n2 j))))))
TEST-PALINDROMES
* (test-palindromes 1089 9801)
#<COMPILED-LEXICAL-CLOSURE (:INTERNAL TEST-PALINDROMES) #x21012A08CF>
* (funcall * "ALAS" "SALA")
T

如果您出于某种原因,以您在宏中的方式(即代替loop)展开lambda的内容,请执行包含{{and的{​​{1}}表单1}}比较),您也可以使用函数执行此操作:

char=

但可能这种做法没有用。

答案 1 :(得分:2)

你的宏只能使用文字数字参数,因为在编译时参数将是数字,但是你已经注意到宏不知道表达式将具有什么值,因此你需要将参数视为变成价值观的表达。您不能对表达式执行digits,因为它可能是变量或类似(+ 3 4)的表达式,并且宏将其作为数据获取。

宏仅用于语法转换。想象一下,我将完成这个功能:

(defun check-numbers (num1 num2)
  (let ((same (= num1 num2))
        (palinfrom (test-palindromes num1 num2)))
    ...))

Common Lisp可以在创建时编译此函数,因此它将扩展所有宏,并包含test-palindromes,其中符号num1num2作为参数。宏需要扩展为代码,通常会评估这两个以获取数字,然后它可以对该结果执行digits。它不可能在编译时发生。它与此相同:

(cond ((test x) ...)
      (t ...))

cond是一个将其转换为if的宏。它获得的值是代码作为结构。在此扩展时,testx很可能不存在,因此宏只能使用它来生成此内容:

(if (test x)
    ...
    ...)

根据经验,如果它可以成为一个函数,你不应该做任何宏。