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.)
To turn make-canned-format
into a function, you need to replace
function
with
compile
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.
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:
(defmacro lambda-format ((&rest args) template)
`(lambda ,args (format nil ,template ,@args)))
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:
(defun curry-format (template)
(lambda (&rest args)
(apply #'format nil template args)))
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:
(defmacro template ((&rest args) template)
(let ((format-fn (gensym))
(template-fn (copy-symbol :template)))
`(let ((,format-fn (formatter ,template)))
(flet ((,template-fn ,args (funcall ,format-fn nil ,@args)))
(function ,template-fn)))))
Here I am using FLET so that the generated function has a user-friendly name, but you could use a lambda too.
(template (a b) "~x ~b")
#<FUNCTION (FLET "TEMPLATE") {1002B93D0B}>
If you call describe on it, you might see that the signature is precise:
Lambda-list: (A B)
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:
(template (a b) "~x ~!")
;; error in FORMAT: Unknown directive (character: EXCLAMATION_MARK)
;; ~x ~!
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).
(defun make-canned-format (template field-names)
#'(lambda field-names (apply #'format `(nil ,template ,@field-names))))
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 (a b c) ; (a b c) this is a list of parameters.
; This list is not evaluated.
...)
(lambda foo ; foo is not allowed syntax. Common Lisp expects a list.
...)
LAMBDA
uses ordinary lambda lists.