【问题标题】:Can I use lambda with an on-the-fly lambda list (without macros)?我可以将 lambda 与动态 lambda 列表(没有宏)一起使用吗?
【发布时间】:2018-09-14 20:03:27
【问题描述】:

我正在尝试创建一个函数来返回函数,以及动态生成的任意 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"

这正是我想要的行为。它返回一个函数,其 lambda 列表是动态提供的(在这种情况下,(fuel device)。)我正在尝试做正确的 Lisp 重构事情并取消不必是宏的宏。但是,我在尝试将任意 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,它的可读性不如宏。)

【问题讨论】:

    标签: lisp common-lisp sbcl


    【解决方案1】:

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

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

    【讨论】:

      【解决方案2】:

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

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

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

      (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 的指定方式,如果实际参数的数量与预期参数的数量不同,您将不会收到警告。如果给定参数太少,则在运行时会出错,如果给定参数太多,则将未使用的参数列表作为返回值(也可以检查该列表以给出错误)。

      【讨论】:

      • 感谢您提供的所有细节!但是,我发布的代码是一个“最小、完整、可验证”的示例,我构建它只是为了举例说明我正在尝试做的事情。我正在处理的实际代码有很多其他活动部分,为了清楚起见,我选择省略了这些部分,但有可能出现 XY 问题。我仍然很高兴我减少了它,因为我提出问题的部分原因是为了更好地理解 Lisp,而且你给了我很多信息来处理,所以谢谢你! :)
      【解决方案3】:
      (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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-09-19
        • 1970-01-01
        • 2016-02-28
        • 1970-01-01
        • 1970-01-01
        • 2014-02-19
        相关资源
        最近更新 更多