Reputation: 2161
I'm trying to create a bunch of cookie-cutter functions and stick 'em in a hash. So far, I've got a macro that expands into such a function:
(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)))
That repetitive setf
, gethash
, make-canned-format-macro
pattern is awfully boilerplate-y, so I tried converting it to a loop:
(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))))
Unfortunately, this blows up because make-canned-format-macro
is operating on the value PARAMS
instead of the value OF params
, because it's getting macroexpanded at compile time, not evaluated at runtime. But as I learned when I asked this question, make-canned-format-macro
won't work as a function, because it needs to construct the lambda
form at compile time. (At least, I think that's what I learned from that? Please tell me I'm wrong about this point! I'd love to have my function-factory be a function, not a macro!)
My current thought is to write a turn-this-list-of-templates-into-make-canned-format-macro-forms
macro instead of a loop. Is that the right thing to do (or at least a non-insane thing to do), or is there a better way?
Upvotes: 2
Views: 88
Reputation: 139261
Since you know the arguments at compile/macro-expansion time, you won't need apply:
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
There is also no need to double quote things in a list:
'('(a))
Code like that is very unusual.
Code generation at runtime
The name -macro
makes no sense, since it makes a function.
The function needs to generate executable code: either use EVAL
or use 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
Construction via macros
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))))
In your code you would write at top-level:
(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))))
Upvotes: 4
Reputation: 675
You absolutely can do what you want as a function. It won't be the prettiest code, but it will work. The key thing that you took away from macros is correct: they are evaluated at compile time[1] I took a look at the other question you referenced and while they were giving you some good optimization/refactoring advice, sometimes you just want what you want. So I've tried to minimally change your code to get it working with one change: rather than using a macro which is eval'd at compile time, I've made it a function which generates the code which is then eval'd at run time:
(defun make-canned-format (template field-names)
(eval `(lambda ,field-names
(apply #'format `(nil ,,template ,,@field-names)))))
Now you should be able to do whatever makes sense for the mass definition of your functions (i.e. wrap in macros, loops, whatever) Something to keep in mind about this approach is that the performance of repeated calls with the same template/field names will be dismal (since it is blindly regenerating the same source code and eval it every call at run time vs. a macro whose definition would just eval once at compile time.) But since you appear to be calling it once per pair of parameters and storing the result of the generation, this won't be an issue.
[1] unless you use the approach used here to generate the macro and eval it at run time. This can be confusing and even harder to debug than macros already can be, but it can be done.
Upvotes: 1