【问题标题】:Processing Parameters Passed to defmacro in Common LispCommon Lisp 中传递给 defmacro 的处理参数
【发布时间】:2017-10-06 00:05:49
【问题描述】:

在尝试解决 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)))))))

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

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'ed 的东西被跳过了,但我并没有真正理解循环中发生了什么。为什么pos 不再被识别?这个循环可以工作吗?任何意见表示赞赏。

谢谢, 保罗

【问题讨论】:

    标签: lambda macros common-lisp


    【解决方案1】:

    defmacro 正文中的代码在 REPL 或文件中解释或编译代码时执行。在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
    

    如果您出于某种原因以您在宏中所做的方式展开 lambda 的内容(即,执行包含 char= 比较的 and 表单,而不是 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))))))
        (eval `(lambda (n1 n2) 
                 (and ,@(loop for i in hash
                              for j from 0
                              collecting `(char= (char n1 ,(cdr i)) (char n2 ,j))))))))
    

    但这种方法可能没有用。

    【讨论】:

    • 常规宏在编译时扩展(在编译代码中;解释器可以在运行时扩展它们),而不是读取时。
    【解决方案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)
        ...
        ...)
    

    根据经验,如果可以将其设为函数,则不应将其设为宏。

    【讨论】:

      猜你喜欢
      • 2011-07-26
      • 1970-01-01
      • 2019-11-03
      • 2013-07-26
      • 1970-01-01
      • 2020-07-12
      • 1970-01-01
      • 2021-02-15
      • 1970-01-01
      相关资源
      最近更新 更多