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

欢迎!请参阅关于页面,了解更多关于如何使用该功能的信息。

+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(参数个数):关键字和映射,这样它们可以条件性地关联。通过threading宏组合所有内容。只适用于您拥有这些函数的情况。

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

在调用站点执行检查,如果存在则关联。在我看来这真的很丑,想象一下多个可选项 - 如何避免嵌套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
  ))

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

另一个好选项是合并,这是一个将多个部分(可能包括空的部分)合并在一起的优秀工具。如果height返回null或包含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
  ))
在非常规则的情况下,我在很多属性上做相同的事情时,我有时会使用宏来实现第一个示例,以移除所有重复的部分。这通常不够通用,不值得重用,但如果需要,它可以减少示例中的重复(就我个人而言,重复并不困扰我,因为它非常清楚地表明了它在做什么)。
在听到Rich的讲话后,关于在散列图中nil这一问题,使我写下了https://corfield.org/blog/2018/12/06/null-nilable-optionality/,以及如何让seancorfield/next.jdbc在映射中保留SQL的null值,同时提供了next.jdbc.optional/as-maps作为将SQL结果集转换为不包含SQL null值的映射的方法。
基本上所有人员属性都连接到一行中。我使用subs获取height部分,如果返回值不为空,则返回一些转换。cond->将会做,我只是在let中收集也许为nil的返回值。
投票 +1

我有时使用的另一种选项是简单地创建带有 nil 的地图,然后在直接移除它们

(->> {:a 10
      :b nil}
 (remove (comp nil? val))
 (into {}))
我认为最好(对性能和清晰度都有利)是首先避免在集合中放置 nil。
这对于处理产生带有 nil 的 map 的库函数很有用,例如对于 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)))))
...