2024 Clojure状态调查! 分享你的想法。

欢迎!请查看 关于 页面以获取更多有关此如何工作的信息。

+4
Clojure
编辑

在 Clojure/conj 会上,《有效的程序》和《也许不》这两篇演讲中,Rich Hickey 强调,如果我们不知道某些信息,我们应该将其排除在外,也就是说,我们应该不在映射中有keyword-nil (spec/nilable) 对。

如何处理这种情况的最佳方法是什么?

(defn person
  [line]
  {:person/name   (name   line)   ;; req
   :person/height (height line)}) ;; opt, height may return nil

我想到以下解决方案:

为 'opt' 函数添加额外的arity:keyword和map,这样它们可以条件性地使用assoc。通过threading宏组合所有内容。仅适用于你“拥有”这些函数的情况下。

(defn person
  [line]
  (->> {:person/name (name line)}
       (height line :person/height)))

在调用站点上检查,并在有某些情况下使用关联。在我看来这非常丑陋,想象一下多个可选项——如何避免if-let的嵌套?”

(defn person
  [line]
  (let [ret {:person/name (name line)}]
    (if-let [height (height line)]
      (assoc ret :person/height height)
      ret)))

4 个答案

+7

已选择
 
最佳答案

首先,我想知道是什么在 height 中触发了这个条件。既然这样,cond-> 就介入其中,这成了我的最爱工具。

(defn person
  [line]
  (cond-> {:person/name (name line)}  ;; non-optional stuff goes here
    (height? line) (assoc :height (height line))
    (weight? line) (assoc :weight (weight line))
    ;; and so on for each optional thing
  ))

请看一个真实代码示例(该项目中还有其他多个示例)。

另一个很好的选项是合并,这是一个将多个部分(以及可能为空的部分)整合在一起的好工具。如果高度返回 nil 或者包含高度信息的映射,则看起来像这样:

(defn person
  [line]
  (merge {:person/name (name line)}
         (height line)  ;; nil or {:height val}
         ;; and so on for each optional thing or sets of things
  ))
by
对于非常规的情况,我在许多属性上都执行相同的事情,我有时会使用宏来覆盖第一个示例以消除所有重复。这很少是足够的通用来重复使用,但如果需要可以干燥那个示例(对我来说,重复并不麻烦,因为它非常清楚它在做什么)。
by
考虑这个问题,整个关于散列映射中 nil 的问题,听了 Rich 的演讲后,启发我写了https://corfield.org/blog/2018/12/06/null-nilable-optionality/,以及 seancorfield/next.jdbc 如何制定了在散列映射中保留 SQL null 值的默认值,同时提供了 next.jdbc.optional/as-maps 作为将 SQL 返回集转换为不包含 SQL null 值的散列映射的方法。
by
编辑了 by
基本上所有人的属性都会连接成一行。我使用 subs 从 height 部分取出来,如果 not str/blank? 返回一些转换。cond-> 会做,我只需在 let 中收集可能为 nil 的返回值。
+1

有时我会使用另一个选项,就是简单地将 nil 值放入 map 中,然后再把它们完全删除。

(->> {:a 10
      :b nil}
 (remove (comp nil? val))
 (into {}))
我认为最佳做法(对性能和清晰度都有益)是首先避免在集合中放置 nil 值。
当处理产生带有 nil 值的 map 的库函数时非常有用,例如 clojure.java.jdbc/query(新的 next-jdbc,但做得更好)。
0 投票

编辑了

一个解决方案是使用 plumatic/plumbing 库中的 assoc 变体,比如 assoc-when,但我想其他实用库中也有类似的功能。

https://github.com/plumatic/plumbing/blob/master/src/plumbing/core.cljx#L147

by
`assoc-when` 将丢弃 `false`,而不仅仅是 `nil`
0 投票
by

此模式很常见,并且是各个实用库的一部分。一个简单、无依赖的解决方案

(defn ?assoc
  ([m k v] (if (nil? v) m (assoc m k v)))
  ([m k v & kvs]
  (assert (even? (count kvs)))
  (let [it (.iterator ^Iterable kvs)]
    (loop [m (?assoc m k v)]
      (if (.hasNext it)
        (recur (?assoc m (.next it) (.next it)))
        m)))))
...