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

欢迎!请阅读关于页面,了解有关这个平台的更多信息。

+4
Clojure
编辑

在Clojure/conj会议中,《有效程序》和《但不一定》的演讲中,Rich Hickey指出,如果我们不知道某件事,我们应该把它留在外面,也就是说,在映射中不应该有关键字-nil(spec/nilable)对。

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

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

我想出了以下解决方案

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

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

在调用位置进行检查,并在一些情况下进行assoc。在我看来,这真的很丑,想象一下多个optionals - 如何避免嵌套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 或包含 height 的映射,那么看起来是这样的:

(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
在非常规则的情况下,我在许多属性上执行相同的工作,我有时会通过宏来覆盖第一个示例以消除所有重复。这很少具有足够的通用性以便重复使用,但如果需要,它可以使该示例达到 DRY(Don't Repeat Yourself,不重复自己)的原则(就我个人而言,重复并不困扰我,因为它的功能非常清晰)。
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 来获取高度部分,当-not str/blank? 返回一些转换。cond-> 将执行,我只需将 maybe-nil 返回值收集在 let 中。
+1 支持

有时我会使用的一个方法是简单地创建包含 nil 的地图,然后在直接之后将它们删除。

(->> {:a 10
      :b nil}
 (remove (comp nil? val))
 (into {}))
我认为(对于性能和清晰度来说)最好是从一开始就避免在集合中放置 nil。
这在处理生成包含 nil 地图(例如 clojure.java.jdbc/query(新的 'next-jdbc',它做得更好))的库函数时很有用。

一个解决方案是使用来自 plumatic/plumbing 库的 assoc 变体,例如 assoc-when,但我相信其他实用库中也有类似的函数。

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

这个`assoc-when`会丢弃`false`值,而不仅仅是`nil`值。

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

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