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

欢迎!请参阅关于页面,了解有关此页工作方式的更多信息。

+4投票
Clojure
编辑

在Clojure/conj对话Effective ProgramsMaybe Not中,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,以便它们可以条件性地assoc。通过线程宏将一切组合在一起。仅适用于您“拥有”这些函数的情况。

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

检查调用位置并使用some来assoc。这在我看来真的很丑,想象一下多个optional - 如何避免嵌套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化(就我个人而言,重复并不让我烦恼,因为它非常清晰易懂)。
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部分,当不为str/blank?时返回一些转换。cond->将进行,我只是把可能为nil的返回值聚集在let中。
+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)))))
...