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

Generating arbitrarily-parameterized functions in a loop

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

(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 正在对 value PARAMS 而不是 OF[=34= 的值进行操作] params,因为它在编译时进行宏扩展,而不是在运行时求值。但是正如我在询问 时了解到的那样,make-canned-format-macro 不会作为函数工作,因为它需要在 编译时 构造 lambda 形式]. (至少,我认为这是我从中学到的东西?请告诉我我在这一点上错了!我希望我的函数工厂是一个函数,而不是宏!)

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

你绝对可以做你想做的事。它不会是最漂亮的代码,但它会起作用。你从宏中拿走的关键是正确的:它们是在编译时评估的[1]你想要什么。因此,我尝试对您的代码进行最低限度的更改,以使其在一次更改后工作:我没有使用在编译时求值的宏,而是将其设为生成代码的函数,然后在运行时间:

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

现在你应该能够做任何对你的函数的大量定义有意义的事情(即包装在宏、循环等中)关于这种方法要记住的一点是重复调用相同的性能template/field 名称将令人沮丧(因为它盲目地重新生成相同的源代码并在 运行 时间每次调用时对其进行评估,而宏的定义只会在编译时评估一次。)但是既然你出现了每对参数调用一次并存储生成的结果,这不是问题。

[1] 除非您使用此处使用的方法 生成 宏并在 运行 时评估它。这可能会造成混淆,甚至比宏更难调试,但可以做到。

因为你在 compile/macro-expansion 时知道参数,你不需要申请:

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))))