I'm trying to create a function to return functions, with arbitrary lambda lists, generated on the fly. I can do it with macros, but I'm trying to de-macro-ify what I've already got:
(defmacro make-canned-format-macro (template field-names)
`(function (lambda ,field-names
(apply #'format `(nil ,,template ,,@field-names)))))
I can use it as follows:
* (make-canned-format-macro "~A-powered ~A" (fuel device))
#<FUNCTION (LAMBDA (FUEL DEVICE)) {10067D975B}>
* (setf (fdefinition 'zoom-zoom) (make-canned-format-macro "~A-powered ~A" (fuel device)))
#<FUNCTION (LAMBDA (FUEL DEVICE)) {1006835A5B}>
* (zoom-zoom "nuclear" "pogo stick")
"nuclear-powered pogo stick"
This is exactly the behavior I want. It returns a function whose lambda list was supplied on the fly (in this case, (fuel device)
.) I'm trying to do the Proper Lisp Refactoring Thing and do away with macros that don't have to be macros. However, I've gotten stuck trying to glom an arbitrary lambda list into a lambda
that's getting executed in a function:
* (defun make-canned-format (template field-names)
#'(lambda field-names (apply #'format `(nil ,template ,@field-names))))
; in: DEFUN MAKE-CANNED-FORMAT
; #'(LAMBDA FIELD-NAMES (APPLY #'FORMAT `(NIL ,TEMPLATE ,@FIELD-NAMES)))
;
; caught ERROR:
; The lambda expression has a missing or non-list lambda list:
; (LAMBDA FIELD-NAMES (APPLY #'FORMAT `(NIL ,TEMPLATE ,@FIELD-NAMES)))
; (SB-INT:NAMED-LAMBDA MAKE-CANNED-FORMAT
; (TEMPLATE FIELD-NAMES)
; (BLOCK MAKE-CANNED-FORMAT
; #'(LAMBDA FIELD-NAMES (APPLY #'FORMAT `(NIL ,TEMPLATE ,@FIELD-NAMES)))))
;
; caught STYLE-WARNING:
; The variable TEMPLATE is defined but never used.
;
; caught STYLE-WARNING:
; The variable FIELD-NAMES is defined but never used.
;
; compilation unit finished
; caught 1 ERROR condition
; caught 2 STYLE-WARNING conditions
MAKE-CANNED-FORMAT
Is what I'm trying to do even possible? (Aside from some hideous eval
hack that would be way less readable than the macro, I mean.)
This is not possible. In a lambda expression, the list of parameters is a list, not an arbitrary symbol which it then evaluates. Common Lisp expects a fixed list of parameters - not a variable. This list is not evaluated:
LAMBDA
uses ordinary lambda lists.First, your macro is overly complex, you do not need to emit a call to apply and build an intermediate argument list if you know in advance how many arguments will be used. Here is another version:
You can get rid of macros by using variadic functions and
APPLY
, but that means that by inspecting only the generated function (using e.g. inspect or describe), you cannot know in advance how many arguments are necessary:In the case of format, you could make use of the
FORMATTER
macro, which is able to parse the template format and warn you before you give it arguments at runtime:Here I am using FLET so that the generated function has a user-friendly name, but you could use a lambda too.
If you call describe on it, you might see that the signature is precise:
This is not the case with the variadic variant.
The macro expects a literal strings, and can checks it contains a valid format during macroexpansion:
Given how
FORMATTER
is specified, you would not be warned if the number of actual arguments differs from the number of expected arguments. You would have an error at runtime if there are too few given arguments, and a list of unused arguments as a return value if there are too many given arguments (that list could be check to give an error too).To turn
make-canned-format
into a function, you need to replacefunction
withcompile
or(coerce (lambda ...) 'function)
.However, your refactoring is misguided.
make-canned-format
should be a macro - this way it will produce a closure in the current compilation environment. The function, however, will produce a closure in the global environment.