【问题标题】:Common Lisp: How to pass a keyword argument iff it has been passed to meCommon Lisp:如果已将关键字参数传递给我,如何传递它
【发布时间】:2021-02-15 23:25:30
【问题描述】:

我一次又一次地发现,函数 A 需要使用或不使用关键字参数来调用函数 B,这取决于是否已将此类关键字参数提供给函数 A。

也许一个愚蠢的 MWE 更容易理解:让我们问问一个叫 Tommy 的男孩,他在学校的一天过得怎么样:

(defun answer (&key (math "boring" math-tested?)
                    (physics "heavy" physics-tested?))
  (if math-tested?
      (if physics-tested?
          (format t "I got an ~A from math and a ~A from physics!" math physics)
          (format t "I got an ~A from math." math))
      (if physics-tested?
          (format t "I got an ~A from physics." physics)
          (format t "Math was ~A and physics was ~A." math physics))))

(defun father-son-talk (&key (math nil math-tested?)
                             (physics nil physics-tested?))
  (format t "Hello Tommy, how was school today?~%")
  (cond ((and math-tested? physics-tested?)
         (answer :math math :physics physics))
        (math-tested?
         (answer :math math))
        (physics-tested?
         (answer :physics physics))
        (t (answer))))

这按预期工作,但由于以下几个原因令人厌烦:

  1. answer 的调用基本上是重复的。如果answer 除了关键字参数之外还有几个普通参数,我必须格外小心地在所有四种情况下都这样做。更糟糕的是维护这样的东西。

  2. 复杂性随着关键字参数的数量呈指数增长。如果父亲也问英语、地质和汤米的午餐怎么办?

  3. 如果父子有不同的默认参数(可能会写成(answer :math math :physics physics)),很容易引起进一步的混淆。

问题:假设answer是我必须遵守的接口的一部分,我该如何简化father-son-talk?理想情况下,我想要类似的东西

(defun father-son-talk (&key (math nil math-tested?)
                             (physics nil physics-tested?))
  (format t "Hello Tommy, how was school today?~%")
  (answer :math (math math-tested?) :physics (physics physics-tested?)))

【问题讨论】:

    标签: common-lisp keyword-argument optional-arguments


    【解决方案1】:

    对此的常见解决方案是apply

    (defun father-son-talk (&rest all-options &key math physics)
      (declare (ignore math physics))
      (format t "Hello Tommy, how was school today?~%")
      (apply #'answer all-options))
    

    如果father-son-talk 本身根本不需要关键字参数,您可以进一步简化它

    (defun father-son-talk (&rest all-options)
      (format t "Hello Tommy, how was school today?~%")
      (apply #'answer all-options))
    

    你可以用这样的函数做一个很好的技巧是说你确实想要关键字参数,并且你接受 any 关键字参数,然后将它们传递给实现函数:

    (defun father-son-talk (&rest all-options &key &allow-other-keys)
      (format t "Hello Tommy, how was school today?~%")
      (apply #'answer all-options))
    

    这和以前的版本一样,除了(father-son-talk 1) 现在是一个错误:它的参数必须都是关键字参数。您想要哪个取决于 answer 是否可能需要非关键字参数,因此两者都可能很有趣。

    还有一个很好的例子,一个函数本身需要关键字参数,但希望集合是可扩展的:函数获取的单个 &rest 参数实际上是一个属性列表,所以你可以这样做:

    (defun foo (&rest plist &key &allow-other-keys)
      ...
      (getf ':x plist 1) ...)
    

    【讨论】:

    • 我在您的回答中添加了一些其他案例,我认为这些案例值得一提:我希望没关系。如果您显然不喜欢这些更改,请拒绝编辑。
    • 嗨@tfb,我对你的改变很好。感谢您的“合作”:-)
    • 谢谢你们。这是我所希望的那种 lispy 解决方案:乍一看不是很合理,但由于所有内容都是列表,因此第二眼看起来非常聪明。
    猜你喜欢
    • 1970-01-01
    • 2011-07-26
    • 2013-07-26
    • 2020-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-03
    • 1970-01-01
    相关资源
    最近更新 更多