我在spec方面做很多工作,我经常遇到的一个问题是构造一个具有可选字段的spec对象。
一个常见的模式如下所示
(assoc {::required-field :value}
::optional-field (when some-condition
:optional-value))
然而,这个模式有一个重大问题:如果::optional-field
位于spec/keys
的:opt
字段,并且这个字段不可为nil,那么无论哪个不包括可选字段的对象都会根据该spec被认为是无效的,因为这些对象将具有一个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
,则删除该键。