在循环中生成任意参数化的函数

时间:2018-09-25 22:58:45

标签: macros lisp common-lisp code-generation

我正在尝试创建一堆千篇一律的函数,并将它们粘贴在哈希中。到目前为止,我有一个宏可以扩展为这样的功能:

(defmacro make-canned-format-macro (template field-names)
  `(function (lambda ,field-names
               (apply #'format `(nil ,,template ,,@field-names)))))

(defparameter *cookie-cutter-functions* (make-hash-table))

(setf (gethash 'zoom-zoom *cookie-cutter-functions*)
      (make-canned-format-macro "~A-powered ~A" (fuel device)))
(setf (gethash 'delicious *cookie-cutter-functions*)
      (make-canned-format-macro "~A ice cream" (flavor)))
(setf (gethash 'movie-ad *cookie-cutter-functions*)
      (make-canned-format-macro "~A: A ~A and ~A film" (title star co-star)))

那个重复的setfgethashmake-canned-format-macro模式非常简单,因此我尝试将其转换为循环:

(loop
  for template in '(('zoom-zoom "~A-powered ~A" (fuel device))
                    ('delicious "~A ice cream" (flavor))
                    ('thematic "~A: A ~A and ~A film" (title star co-star)))
  do (let ((func-name (car template))
           (format-string (cadr template))
           (params (caddr template)))
        (setf (gethash func-name *cookie-cutter-functions*)
              (make-canned-format-macro format-string params))))

不幸的是,由于make-canned-format-macro PARAMS而不是值 OF params上运行,因此炸毁了它会在编译时进行宏扩展,而不是在运行时进行评估。但是,正如我在询问this question时所了解的那样,make-canned-format-macro不能用作函数,因为它需要在编译时构造lambda形式。 (至少,我认为那是我从中学到的东西?请告诉我我在这一点上错了!我希望我的函数工厂成为函数,而不是宏!

我目前的想法是编写一个turn-this-list-of-templates-into-make-canned-format-macro-forms宏而不是一个循环。是正确的做法(或至少是非疯狂的事情),还是有更好的方法?

2 个答案:

答案 0 :(得分:4)

由于您知道编译/宏扩展时的参数,因此无需应用:

CL-USER 35 > (defmacro make-canned-format-macro (template field-names)
               `(function (lambda ,field-names
                            (format nil ,template ,@field-names))))
MAKE-CANNED-FORMAT-MACRO

CL-USER 36 > (macroexpand-1 '(make-canned-format-macro "~A-powered ~A" (fuel device)))
(FUNCTION (LAMBDA (FUEL DEVICE) (FORMAT NIL "~A-powered ~A" FUEL DEVICE)))
T

也无需在列表中双引号:

'('(a))

像这样的代码非常不寻常。

在运行时生成代码

名称-macro没有意义,因为它具有功能。 该函数需要生成可执行代码:使用EVAL或使用COMPILE

CL-USER 56 > (defun make-canned-format-function (template field-names)
               (compile nil `(lambda ,field-names
                               (format nil ,template ,@field-names))))
MAKE-CANNED-FORMAT-FUNCTION


CL-USER 57 > (loop
              for (func-name format-string params)
              in '((zoom-zoom "~A-powered ~A"        (fuel device))
                   (delicious "~A ice cream"         (flavor))
                   (thematic  "~A: A ~A and ~A film" (title star co-star)))
              do (setf (gethash func-name *cookie-cutter-functions*)
                       (make-canned-format-function format-string params)))
NIL

通过宏进行构造

CL-USER 77 > (defun make-canned-format-function-code (template fields)
               `(lambda ,fields
                  (format nil ,template ,@fields)))
MAKE-CANNED-FORMAT-FUNCTION-CODE

CL-USER 78 > (defmacro def-canned-format-functions (ht description)
               `(progn ,@(loop
                          for (func-name format-string params) in description
                          collect `(setf (gethash ',func-name ,ht)
                                         ,(make-canned-format-function-code format-string params)))))
DEF-CANNED-FORMAT-FUNCTIONS

CL-USER 79 > (pprint
              (macroexpand-1
               '(def-canned-format-functions
                 *foo*
                 ((zoom-zoom "~A-powered ~A"        (fuel device))
                  (delicious "~A ice cream"         (flavor))
                  (thematic  "~A: A ~A and ~A film" (title star co-star))))))

(PROGN
  (SETF (GETHASH 'ZOOM-ZOOM *FOO*)
        (LAMBDA (FUEL DEVICE)
          (FORMAT NIL "~A-powered ~A" FUEL DEVICE)))
  (SETF (GETHASH 'DELICIOUS *FOO*)
        (LAMBDA (FLAVOR)
          (FORMAT NIL "~A ice cream" FLAVOR)))
  (SETF (GETHASH 'THEMATIC *FOO*)
        (LAMBDA (TITLE STAR CO-STAR)
          (FORMAT NIL "~A: A ~A and ~A film" TITLE STAR CO-STAR))))

在您的代码中,您将在顶层编写:

(def-canned-format-functions
   *foo*
   ((zoom-zoom "~A-powered ~A"        (fuel device))
    (delicious "~A ice cream"         (flavor))
    (thematic  "~A: A ~A and ~A film" (title star co-star))))

答案 1 :(得分:1)

您绝对可以做您想做的事情。它不会是最漂亮的代码,但是会起作用。您从宏中删除的关键是正确的:它们是在编译时进行评估的[1],我查看了您所引用的另一个问题,尽管它们为您提供了一些很好的优化/重构建议,但有时您只是想要您想要的想。因此,我尝试了最小程度地更改代码以使其能够进行一次更改的方法:不是使之使用在编译时评估的宏,而是使它成为一个函数,该函数生成在随后评估的代码运行时间:

(defun make-canned-format (template field-names)
    (eval `(lambda ,field-names
        (apply #'format `(nil ,,template ,,@field-names)))))

现在,您应该能够执行对函数的大规模定义有意义的任何事情(即,包装宏,循环等),这种方法要记住的一点是,使用相同的模板/字段名将是令人沮丧的(因为它盲目地重新生成了相同的源代码,并且在运行时对每个调用都进行了评估,而宏的定义在编译时只是进行了一次评估。)但是由于您似乎每对调用一次,参数并存储生成的结果,这将不是问题。

[1],除非您使用此处使用的方法生成宏并在运行时对其进行评估。与宏相比,这可能会令人困惑,甚至难以调试,但是可以做到。