我可以将 lambda 与即时 lambda 列表(没有宏)一起使用吗?

Can I use lambda with an on-the-fly lambda list (without macros)?

我正在尝试为 return 函数创建一个函数,其中包含动态生成的任意 lambda 列表。我可以用宏来做到这一点,但我正在尝试去宏化我已经拥有的东西:

(defmacro make-canned-format-macro (template field-names)
  `(function (lambda ,field-names
               (apply #'format `(nil ,,template ,,@field-names)))))

我可以这样使用:

* (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"

这正是我想要的行为。它 return 是一个函数,其 lambda 列表是动态提供的(在本例中,(fuel device)。)是宏。但是,我在尝试将任意 lambda 列表 glom 到 lambda 中时遇到了困难,该 lambda 在函数中执行:

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

我想做的事情有可能吗? (除了一些可怕的 eval hack 之外,我的意思是,它的可读性不如宏。)

要将make-canned-format变成一个函数,需要替换 functioncompile(coerce (lambda ...) 'function).

但是,您的重构被误导了。 make-canned-format 应该 是一个宏 - 这样它会产生 current compilation environment 中的闭包。 但是,该函数将在 global environment.

中产生一个闭包

首先,您的宏过于复杂,如果您事先知道将使用多少参数,则无需发出应用调用并构建中间参数列表。这是另一个版本:

(defmacro lambda-format ((&rest args) template)
  `(lambda ,args (format nil ,template ,@args)))

您可以通过使用可变参数函数和 APPLY 来摆脱宏,但这意味着通过仅检查生成的函数(例如使用 inspect 或 describe),您无法提前知道需要多少参数:

(defun curry-format (template)
  (lambda (&rest args)
    (apply #'format nil template args)))

在格式的情况下,您可以使用 FORMATTER 宏,它能够解析模板格式并在您给它参数之前警告您在运行时:

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

我在这里使用 FLET,以便生成的函数具有用户友好的名称,但您也可以使用 lambda。

(template (a b) "~x ~b")
#<FUNCTION (FLET "TEMPLATE") {1002B93D0B}>

如果您对其调用 describe,您可能会看到签名是准确的:

Lambda-list: (A B)

可变变量不是这种情况。

宏需要一个文字字符串,并且可以在宏扩展期间检查它是否包含有效格式:

(template (a b) "~x ~!")
 ;; error in FORMAT: Unknown directive (character: EXCLAMATION_MARK)
 ;;  ~x ~!

鉴于 FORMATTER 的指定方式,如果实际参数的数量与预期参数的数量不同,您将不会收到警告。如果给定的参数太少,您将在运行时遇到错误,如果给定的参数太多,则将未使用的参数列表作为 return 值(也可以检查该列表以给出错误)。

(defun make-canned-format (template field-names)
   #'(lambda field-names (apply #'format `(nil ,template ,@field-names))))

这是不可能的。在 lambda 表达式中,参数列表是一个列表,而不是它随后计算的任意符号。 Common Lisp 需要一个固定的参数列表——而不是一个变量。此列表未评估:

(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 使用 ordinary lambda lists.