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

检查调用位置,如果some的话,就assoc。在我看来,这真的很丑,想象一下有多个可选退路 - 如何避免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
  ))
对于非常规的情况,即我在许多属性上执行相同操作时,我会有时使用宏覆盖第一个示例以删除所有重复部分。这很少更新到足够通用的层面以便重复使用,但如果需要,它可以让该示例简化(我个人觉得重复并不烦恼,因为它的功能非常清晰)。
考虑到在散列表中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->`将会执行,我只是将可能为`nil`的返回值汇总到`let`中。
+1

我有时会使用的一种方法是简单地创建包含nil的映射,然后立即将其移除。

(->> {:a 10
      :b nil}
 (remove (comp nil? val))
 (into {}))
我认为最好的办法(既适用于性能也适用于清晰性)是首先避免在集合中放入nil值。
这在处理像clojure.java.jdbc/query这样的库函数时很有用,这些函数会产生包含nil值的映射。
0

编辑了

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

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

此 `assoc-when` 会丢弃 `false`s,而不仅仅是 `nil`s
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)))))
...