在循环中生成任意参数化函数
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)))
重复的 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
正在对 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))))
我正在尝试创建一堆千篇一律的函数并将它们放在哈希中。到目前为止,我有一个扩展成这样一个函数的宏:
(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
正在对 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))))