使用带有宏的动态输入来构建函数

时间:2018-06-21 10:03:16

标签: macros arguments common-lisp

我的目标是拥有一个宏,该宏可以自动构建函数,并带有在其他位置生成的参数列表。我希望宏返回一个由函数及其使用的参数列表(符号列表)组成的列表。我正在使用SBCL。

生成参数列表

假设参数列表由生成:

(defun input-syms ()
  (list 'in1 'in2 'in3))
;;=> (IN1 IN2 IN3)

使用此参数列表构建函数

Nested `defun` produces a repeated warning in Allegro Common Lisp中给出有用的答案之后,我像这样使用labels(仅出于示例目的,添加列表的元素):

(defmacro create-funtest ()
  (let ((input-list (input-syms)))
    `(labels ((fun-created ,input-list
                (reduce #'+ (list ,@input-list))))
       #'fun-created)))
(funcall (create-funtest) 2 2 3) ;=> 7

这似乎可行,尽管我认为可以有一种更简单的方法。 (list ,@input-list)似乎是不必要的,但仅用,input-list替换它是行不通的。

返回参数列表和函数

这是我不知所措的地方,它似乎与,input-list的确切含义有关​​。我认为这与以下事实有关:我们正在操纵符号,因此我尝试在其中插入symbol-value,但无济于事。

不起作用代码表示的我想要获得的是:

(defmacro create-funtest2 ()
  (let ((input-list (input-syms)))
    `(labels ((fun-created ,input-list
                (reduce #'+ (list ,@input-list))))
       (list #'fun-created ,input-list))))

必须返回:(#<FUNCTION ...> (IN1 IN2 IN3))。 但是调用create-funtest2会产生编译错误The variable IN2 is unbound.。我认为它正在尝试评估符号,而不是按原样给我符号。

我需要能够获得用于构建函数的符号,我在随后调用函数时使用它们来知道哪个输入是什么。符号列表也可以通过create-funtest宏进行修改,因此我真的需要从宏内部获取它。

编辑1

感谢Rainer Joswig的回答。困扰我的事情实际上是将符号列表作为符号返回。我猜create-funtest2的扩展代码(从您给出的扩展中)应该看起来像:

(LABELS ((FUN-CREATED (IN1 IN2 IN3)
          (REDUCE #'+ (LIST IN1 IN2 IN3))))
  (LIST #'FUN-CREATED (LIST 'IN1 'IN2 'IN3)))

因此宏的输出为(#<FUNCTION ...> (IN1 IN2 IN3))。 问题是我想评估input-list,但将其元素保留为符号(不确定对不起,我措辞不正确)。

编辑2

谢谢coredump。 create-funtest2的工作版本为:

(defmacro create-funtest2 ()
  (let ((input-list (test-input-syms)))
    `(labels ((fun-created ,input-list
                (reduce #'+ (list ,@input-list))))
       (list #'fun-created (quote ,input-list)))))

提供了哪些扩展名(感谢Rainer的代码段):

(let ((*print-circle* t)
          (*PRINT-RIGHT-MARGIN* 50))
      (pprint (copy-tree (macroexpand-1 '(create-funtest2)))))
=>
(LABELS ((FUN-CREATED (IN1 IN2 IN3)
           (REDUCE #'+ (LIST IN1 IN2 IN3))))
  (LIST #'FUN-CREATED '(IN1 IN2 IN3)))

并通过以下方式调用:

(defparameter *fun-created2* (create-funtest2))
(funcall (car *fun-created2*) 1 2 3) ; => 6, OK
(second *fun-created2*) ; => (IN1 IN2 IN3), OK

2 个答案:

答案 0 :(得分:2)

输入列表为(IN1 IN2 IN3)

这有效:

(reduce #'+ (list IN1 IN2 IN3))

这不起作用:

(reduce #'+ (IN1 IN2 IN3))

原因:没有功能IN1

Macroexpand是您的朋友

CL-USER 58 > (let ((*print-circle* t)
                   (*PRINT-RIGHT-MARGIN* 50))
               (pprint (copy-tree (macroexpand-1 '(create-funtest2)))))

(LABELS ((FUN-CREATED (IN1 IN2 IN3)
           (REDUCE #'+ (LIST IN1 IN2 IN3))))
  (LIST #'FUN-CREATED (IN1 IN2 IN3)))

扩展后的代码有两个问题:

  1. 最后一行中的功能IN1不存在
  2. 最后一行中的变量IN2IN3不存在

也许macroexpand将帮助您解决问题。

要获得更多帮助,您需要稍微更好地解释您想做什么。

答案 1 :(得分:2)

简单版本

当给定任意长输入列表时,您可能要使用reduce的原因是,函数调用受CALL-ARGUMENT-LIMIT限制。但是,在这里,您将符号列表用作函数参数,该列表受LAMBDA-PARAMETERS-LIMIT的限制。同样,第二个必须大于或等于第一个。因此,如果符号列表足够短,可以用作参数列表,那么它也足够短,可以用作+调用中的参数。

(defun input-symbols ()
  '(in1 in2 in3))

(defmacro create-funtest ()
  (let ((args (input-symbols)))
    `(lambda ,args (+ ,@args))))

在上面,我还使用了匿名函数,但这并不重要。

返回符号进行评估

使用与上述相同的方法重写的第二个版本是:

(defmacro bad-create-funtest2 ()
  (let ((args (input-symbols)))
    `(list (lambda ,args (+ ,@args))
           ,args)))

macroexpand对此有何评论?

(macroexpand '(bad-create-funtest2))
=> (LIST (LAMBDA (IN1 IN2 IN3) (+ IN1 IN2 IN3))
         (IN1 IN2 IN3))

在这里,您可以看到您正在尝试使用参数in1in2来调用in3。 您不想评估符号列表,只需传递未评估的符号即可。

返回未评估的符号

(defmacro create-funtest2 ()
  (let ((args (input-symbols)))
    `(list (lambda ,args (+ ,@args))
           (quote ,args))))

通过引用该值,可以确保该值不会被评估。

(macroexpand '(create-funtest2))
=> (LIST (LAMBDA (IN1 IN2 IN3) (+ IN1 IN2 IN3))
         '(IN1 IN2 IN3))