请在2024 Clojure状态调查!中分享您的想法。

欢迎!有关如何操作的更多信息,请参阅关于页面。

+4
Clojure
编辑

在Clojure/conj会谈中,Rich Hickey在有效程序也许不是中指出了,如果我们不知道某件事,我们应将其留出,即我们在映射中不应有关键字-null(spec/nilable)对。

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

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

我找到了以下这些解决方案

为'opt'函数添加额外的arity:关键字和映射,以便它们可以条件性地关联。通过threading宏将一切组合起来。仅适用于如果您“拥有”那些函数。

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

在调用站点进行检查,并在some附近关联。在我看来这真的很丑,想想多个可选项 - 如何避免嵌套的if-lets?"

(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
  ))

以下是一个真实代码示例(该项目中还有其他几个示例):请点击此处查看

另一个不错的选择是合并(merge),它是一个将多个部分或空的部分合并在一起的好工具。如果height返回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
  ))
对于非常规例,我在很多属性上做相同的事情时,有时会使用宏来覆盖第一个示例以去除重复内容。这通常不太通用,我不会重用它,但如果需要可以省去重复(就我个人而言,我不介意重复,因为它们非常清晰表明在做什么)。
考虑到这个关于哈希表中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值的哈希表的。

编辑
基本上所有个人属性都连成一行。我使用subs取出高度部分,当不为空时返回一些转换。cond-将进行判断,我只会在let中收集可能为nil的返回值。
+1

我有时使用的一种方法是简单地创建包含空值的映射,然后立即删除它们。

(->> {:a 10
      :b nil}
 (remove (comp nil? val))
 (into {}))
我认为最好(对性能和清晰性)一开始就避免在集合中放置空值。
当处理生成带有空值的映射的库函数时,这很有用,例如用于 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

这种模式很常见,是各种实用库的一部分。一个简单,0 依赖的解决方案

(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)))))
...