我经常与spec配合使用,一个常见的问题是构建具有可选字段的spec'对象。
一个常见模式可能如下所示
(assoc {::required-field :value}
::optional-field (when some-condition
:optional-value))
然而,这种模式有一个重大问题:如果::optional-field
在场spec/keys
:opt
字段不可为nil,那么每个省略了可选字段的对象都将根据该规范无效,因为它们将具有nil
值而不是省略键。
这种方法的替代方法是使用类似下面的cond->
(cond-> {::required-field :value}
some-condition (assoc ::optional-field :optional-value))
这种模式远非cond->
的唯一用途,但它出现频率较高。我认为这很不好,因为它要求条件一直显示在最上面,而且使用某些函数生成:optional-value
时,无法通过nil punning终止,同时仍然生成一个有效值,而不需要额外的流程控制和本地绑定。
此问题的一个更灵活的解决方案是使用if-some
,但这种解决方案很啰嗦,当需要同时包含多个可选字段时,会变得难以控制,如下所示。
(let [m {::required-field :value}
m (if-some [v (produces-optional-value)]
(assoc m ::optional-field v)
m)
m (if-some [v (produces-other-value)]
(assoc m ::other-field v)
m)]
m)
此问题的一个解决方案是函数assoc-some
,它类似于assoc
,但当给定的值为nil
时,它会省略键。这个函数在medley中提供,并被一些所使用。
上面的复杂示例,使用assoc-some
将如下所示
(assoc-some {::required-field :value}
::optional-field (produces-optional-value)
::other-field (produces-other-value))
这可能是解决此问题的唯一方法,但我认为这是一个值得考虑的问题。
这是另一个情况,其中命名解决方案不适用,但它似乎遵循类似的模式,例如在farolero中的这段代码(点击查看),这个问题可以通过一个并行函数(除了我的实现之外尚未在野外找到)update-some
来解决,该函数将更新一个键的新值,如果该值为nil
,则删除该键。