我正在尝试找到一种方法来执行下面这些函数的一般功能,因此您可以放入具有任意多个输入的函数 f,并且程序应该为每个变量执行此嵌入式循环,并将给定的函数应用于每个循环。
(defun one (k1 f)
(funcall k1 (loop for x in domain collect (funcall f x))))
(defun two (k2 k1 f)
(funcall k2 (loop for y in domain collect
(funcall k1 (loop for x in domain collect
(funcall f y x))))))
(defun three (k3 k2 k1 f)
(funcall k3 (loop for z in domain collect
(funcall k2 (loop for y in domain collect
(funcall k1 (loop for x in domain collect
(funcall f z y x))))))
我想做的是 Lisp 中的谓词逻辑,其中 kn 是 all och ex 量词,f 是一个包含 n 个自由变量的公式。 感谢您抽出宝贵时间!
如果要迭代的维数在编译时已知,例如使用此语法,其中
(a b c)
表示存在三个级别的迭代:
(do-domains (a b c) domain
...)
...然后你可以写一个扩展成的宏:
(let ((var domain))
(dolist (a var)
(dolist (b var)
(dolist (c var)
...)))
然而,在一般情况下,您可能希望接受变量的变量列表,在这种情况下,您可以编写递归函数。你正在用不同的变量迭代同一个域,所以我会按如下方式编写函数:
(defun iter-domain-helper (function domain dimension values)
(if (>= dimension 1)
(dolist (value domain)
(iter-domain-helper function
domain
(1- dimension)
(list* value values)))
(apply function values)))
iter-domain-helper
递归维度变量,它是一个整数,表示要生成的变量数。
在 0 级(或以下),它在给定的
apply
上调用 function
,迭代的当前值存储在 values
中。如果 values
的大小与 function
的签名不匹配,则会检测到错误。注意在递归调用中维度是如何减少的。
例如:
(let ((counter 0))
(iter-domain-helper (lambda (a b c)
(incf counter)
(print (list a b c)))
'(0 1 5 8 11)
3
nil)
counter)
这会在标准输出上打印 5^3 = 125 个列表,每个列表的大小为 3.
你不需要让用户直接通过
values
函数,所以实际的解决方案应该更像这样:
(defun iter-domain (function domain dimension)
(iter-domain-helper function domain dimension nil))
我试着写这样一个宏,它允许可变数量的
k
s(Nk
s):
(defmacro %predcall (f domain &rest ks)
(let* ((vars (loop for k in ks collect (gensym)))
(expr `(funcall ,f ,@(reverse vars))))
(loop for ki in ks
for var in vars
do (setf expr `(funcall ,ki (loop for ,var in ,domain collect ,expr)))
finally (return expr))))
您可以通过以下方式进行测试:
(macroexpand-1 '(%predcall f domain k1))
;; (FUNCALL K1 (LOOP FOR #:G5665 IN DOMAIN COLLECT (FUNCALL F #:G5665))) ;
;; T
(macroexpand-1 '(%predcall f domain k1 k2))
;; (FUNCALL K2
;; (LOOP FOR #:G5667 IN DOMAIN COLLECT
;; (FUNCALL K1
;; (LOOP FOR #:G5666 IN DOMAIN COLLECT (FUNCALL F #:G5667 #:G5666))))) ;
;; T
(macroexpand-1 '(%predcall f domain k1 k2 k3))
;; (FUNCALL K3
;; (LOOP FOR #:G5670 IN DOMAIN COLLECT
;; (FUNCALL K2
;; (LOOP FOR #:G5669 IN DOMAIN COLLECT
;; (FUNCALL K1
;; (LOOP FOR #:G5668 IN DOMAIN COLLECT
;; (FUNCALL F #:G5670 #:G5669 #:G5668))))))) ;
;; T
gensym
解决了以独特的方式命名可变数量的变量的问题 - 这样它们就不会相互干扰。
但是,这个辅助宏的调用中参数的顺序根本不是你在问题描述中带来的。
所以,我们可以使用另一个宏来准确获得您想要的语法/接口:
(defmacro predcall (&rest args)
(let* ((args-without-last (butlast args))
(ks (butlast args-without-last))
(f (car (last args-without-last)))
(domain (car (last args))))
`(%predcall f domain ,@(reverse ks))))
现在这个宏以参数的“更正”顺序触发辅助宏的调用:
(macroexpand-1 '(predcall k1 f domain))
;; (%PREDCALL F DOMAIN K1) ;
;; T
(macroexpand-1 '(predcall k2 k1 f domain))
;; (%PREDCALL F DOMAIN K1 K2) ;
;; T
(macroexpand-1 '(predcall k3 k2 k1 f domain))
;; (%PREDCALL F DOMAIN K1 K2 K3) ;
;; T
要测试这些调用是否会正确扩展为宏 - 你可以 编写另一个宏,然后调用内部宏调用的 macroexpand-1 调用。 我们调用这个宏来执行 macroexpand-1 调用:
(defmacro predcall-1 (&rest args)
"macroexpand-1 over the called function"
(let* ((args-without-last (butlast args))
(ks (butlast args-without-last))
(f (car (last args-without-last)))
(domain (car (last args))))
`(macroexpand-1 '(%predcall f domain ,@(reverse ks)))))
(predcall-1 k1 f domain)
;; (FUNCALL K1 (LOOP FOR #:G5675 IN DOMAIN COLLECT (FUNCALL F #:G5675))) ;
;; T
(predcall-1 k2 k1 f domain)
;; (FUNCALL K2
;; (LOOP FOR #:G5677 IN DOMAIN COLLECT
;; (FUNCALL K1
;; (LOOP FOR #:G5676 IN DOMAIN COLLECT (FUNCALL F #:G5677 #:G5676))))) ;
;; T
(predcall-1 k3 k2 k1 f domain)
;; (FUNCALL K3
;; (LOOP FOR #:G5680 IN DOMAIN COLLECT
;; (FUNCALL K2
;; (LOOP FOR #:G5679 IN DOMAIN COLLECT
;; (FUNCALL K1
;; (LOOP FOR #:G5678 IN DOMAIN COLLECT
;; (FUNCALL F #:G5680 #:G5679 #:G5678))))))) ;
;; T
所以你可以看到,你可以按照你想要的顺序输入参数。 (唯一的区别是
domain
作为最后一个参数输入
而您更喜欢将域捕获为闭包)。
所以最后,你可以用这种形式调用宏:
(predcall kn ... k3 k2 k1 f domain)
是不是很神奇,在 Common Lisp 中使用相对简洁的宏可以实现所有这些?