我正在尝试创建一堆千篇一律的函数,并将它们粘贴在哈希中。到目前为止,我有一个宏可以扩展为这样的功能:
(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)))
那个重复的setf
,gethash
,make-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
宏而不是一个循环。是正确的做法(或至少是非疯狂的事情),还是有更好的方法?
答案 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],除非您使用此处使用的方法生成宏并在运行时对其进行评估。与宏相比,这可能会令人困惑,甚至难以调试,但是可以做到。