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

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

+4
Clojure
编辑

In Clojure/conj talks Effective Programs and Maybe Not, Rich Hickey pointed that if we don't know something we should leave it out, i.e. we should not have keyword-nil (spec/nilable) pairs in maps.

如何处理这种情况的最佳方法

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

在调用位置进行检查并使用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

精选 by
 
最佳答案

首先,我会问什么在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或包含高度的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
对于非常规的情况,我在许多属性上重复进行相同的操作,有时我会对第一个示例进行宏定义以消除所有重复。这很少足够泛化以供重用,但它可以在需要时将示例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提取高度部分,当-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

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