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

欢迎!请查看关于页面,了解有关本站运作的更多信息。

+4
Clojure
编辑

在Clojure/conj演讲中,《有效程序》和《也许不是》中,Rich Hickey指出,如果不知道某事物,应该将其排除在外,即我们应该不在map中使用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)))

在调用站点进行检查并assoc some。我觉得 really ugly,想象一下多个选项 - 如何避免嵌套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
  ))

以下是在真实代码中的示例(该项目中还有其他几个示例)。

另一个不错的选择是merge,这是一个将多个部分(可能包括空的部分)合并在一起的好工具。如果height返回nil或包含height的map,那么它看起来像

(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的映射,然后在直接之后将它们移除。

(->> {:a 10
      :b nil}
 (remove (comp nil? val))
 (into {}))
我认为最佳的做法(性能和可读性方面)是避免在集合一开始就放置nil值。
当处理产生包含nil值的映射的库函数时,例如clojure.java.jdbc/query(新版本'next-jdbc'做得更好),这很有用。
0

编辑

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

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

此`assoc-when`将丢弃`false`,而不仅仅是`nil`
0

这种模式很常见,是各种实用库的一部分。一个简单、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)))))
...