在lisp宏的lambda列表中组合& key和& rest的最佳方法是什么?

时间:2017-02-17 16:50:33

标签: macros lisp common-lisp

我使用宏实现了Heap's algorithm。它工作正常,但我想调整它,以便它可以根据需要生成照应或非照应代码。换句话说,我想让宏生成序列的内部副本,它将置换或处理宏外部可用的序列。

我完全不满意,彻头彻尾的令人尴尬的代码是:

;; Anaphoric version
;; To make it non-anaphoric, substitute (,var (copy-seq ,vec)) for (,var ,vec)
(defmacro run-permutations (var vec &rest body)
 "Executes body for all permutations of vec, which is stored in variable var"
  `(let ((,var ,vec))
     (labels ((generate (&optional (n (length ,var)))
       (if (= n 1)
         (progn ,@body)
        (progn 
           (loop for i from 0 below (1- n)
                 do (progn 
                      (generate (1- n))
                      (rotatef (aref ,var (if (evenp n) i 0))
                               (aref ,var (1- n)))))
           (generate (1- n))))))
      (generate))))

? (run-permutations v "123" (pprint v))
"123"
"213"
"312"
"132"
"231"
"321"
?

我想写一些像这样的东西......

? (setf v "123")
? (run-permutations :anaphoric t v "123" (...do stuff...))
? v
"321"

? (setf v "123")
? (run-permutations v "123" (...do stuff...))
? v
"123"

...但我还没有找到令人满意的&rest&key组合或任何其他编写lambda列表的方法。

所以我的问题是:有没有办法实现这一点,最好不要编写更多代码来解析宏的lambda列表?或者是否存在另一种或多或少标准(并且可能更优雅)的解决方案?我强烈怀疑后者。

非常感谢您的意见。与往常一样,对代码的任何其他评论也会受到赞赏。

更新

辉煌!我选择gensym使用n,因为body是在递归中调用的,我无法看到如何从其他地方调用它 - 至少在没有重写所有内容的情况下

我还添加了另一项功能和次要优化。如果您有点好奇,更新后的版本是:

(defmacro do-permutations ((var vec &key anaphoric (len (length vec))) &body body)
  "Executes body for all permutations of vec, which is stored in variable var.
   KEYS:
     anaphoric: if defined, modifies var outside the macro, preserves it otherwise
     len: number of items that will be permuted, default is the full vector"
  (let ((n (gensym)))
  `(let ((,var ,(if anaphoric vec `(copy-seq ,vec))))
     (labels ((generate (&optional (,n ,len))
                (if (= ,n 1)
                    (progn ,@body)
                  (let ((n-1 (1- ,n)))
                    (loop for i from 0 below n-1
                          do (progn
                               (generate n-1)
                               (rotatef (aref ,var (if (evenp ,n) i 0))
                                        (aref ,var n-1))))
                    (generate n-1)))))
       (generate)))))

最后,我尝试在progn之后移除do,但它不起作用,因为此时必须对2个表达式进行评估。

1 个答案:

答案 0 :(得分:4)

正确缩进代码:

(defmacro run-permutations (var vec &rest body)
  "Executes body for all permutations of vec, which is stored in variable var"
  `(let ((,var ,vec))
     (labels ((generate (&optional (n (length ,var)))
                (if (= n 1)
                    (progn ,@body)
                  (progn 
                    (loop for i from 0 below (1- n)
                          do (progn 
                               (generate (1- n))
                               (rotatef (aref ,var (if (evenp n) i 0))
                                        (aref ,var (1- n)))))
                    (generate (1- n))))))
       (generate))))

使用类似:

(do-permutations (v "123" :anaphoric t)
  (some)
  (stuff))

使用宏:

(defmacro do-permutations ((var vec &key anaphoric) &body body)
  ...)

其他名称:doing-permutationswith-permutations,...

另请注意,可以使用&body声明正文,而不是&rest。语义是相同的,但人们希望它的缩进方式不同。 &body表示Lisp表单列表如下。

progn之后,您在loop内也不需要do

body看到变量n。你可能会想到身体的另一个地方......