Common Lisp:如果关键字参数已传递给我,如何传递它
Common Lisp: How to pass a keyword argument iff it has been passed to me
我一次又一次地发现自己处于函数 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))))
这按预期工作,但由于以下几个原因令人厌烦:
对answer
的调用基本都是重复的。如果 answer
除了关键字参数之外还有几个普通参数,我必须格外小心,在所有四种情况下都做同样的事情。更糟糕的是,当维护这样的东西时。
复杂度随着关键字参数的数量呈指数增长。如果爸爸问英语、地质学和汤米的午餐怎么办?
如果父子具有不同的默认参数,则很容易引起进一步的混淆(可能会想写 (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?)))
一个常见的解决方案是 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
是否期望 non-keyword 个参数,因此两者都很有趣。
还有这样一个很好的例子,函数本身需要关键字参数但希望集合是可扩展的:函数获得的单个 &rest
参数实际上是一个 属性 列表,所以你可以这样做:
(defun foo (&rest plist &key &allow-other-keys)
...
(getf ':x plist 1) ...)
我一次又一次地发现自己处于函数 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))))
这按预期工作,但由于以下几个原因令人厌烦:
对
answer
的调用基本都是重复的。如果answer
除了关键字参数之外还有几个普通参数,我必须格外小心,在所有四种情况下都做同样的事情。更糟糕的是,当维护这样的东西时。复杂度随着关键字参数的数量呈指数增长。如果爸爸问英语、地质学和汤米的午餐怎么办?
如果父子具有不同的默认参数,则很容易引起进一步的混淆(可能会想写
(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?)))
一个常见的解决方案是 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
是否期望 non-keyword 个参数,因此两者都很有趣。
还有这样一个很好的例子,函数本身需要关键字参数但希望集合是可扩展的:函数获得的单个 &rest
参数实际上是一个 属性 列表,所以你可以这样做:
(defun foo (&rest plist &key &allow-other-keys)
...
(getf ':x plist 1) ...)