Can I use lambda with an on-the-fly lambda list (w

2019-07-20 21:13发布

问题:

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.)

回答1:

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.



回答2:

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).



回答3:

(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.