请在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。通过螺纹宏组合一切。仅适用于您“拥有”那些函数的情况。

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

检查调用站点并使用some. descend. If imo,非常丑陋,想象多个可选 - 如何避免嵌套if-lets?"

(defn person
  [line]
  (let [ret {:person/name (name line)}]
    (if-let [height (height line)]
      (assoc ret :person/height height)
      ret)))

4 个回答

+7

选择
 
最佳答案

首先,我想知道在高度中什么触发了这个条件。既然如此,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
  ))

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

另一个不错的选择是合并,这是一个将多个部分(甚至可能是空的)东西合并在一起的优秀工具。如果高度返回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来获取高度部分,当不为str/blank?时返回一些转换。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

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