命名空间限定记录字段访问器

Namespace qualified record field accessors

同样的愚蠢错误我已经犯过很多次了:

(defrecord Record [field-name])

(let [field (:feld-name (->Record 1))] ; Whoops!
  (+ 1 field))

由于我拼错了字段名关键字,这会导致NPE。

"obvious" 解决方案是让 defrecord 发出命名空间关键字,从那时起,特别是在不同文件中工作时,IDE 将能够立即在我键入 ::n/ 后立即显示可用的关键字。

我可能可以通过一些创意创建一个宏来包装 defrecord 来为我创建关键字,但这似乎有点过分了。

有没有办法让 defrecord 发出命名空间字段访问器,或者有没有其他好的方法来避免这个问题?

我感受到你的痛苦。值得庆幸的是,我有一个解决方案可以节省很多 times/week 我已经使用了几年。是the grab function from the Tupelo library。它不提供您希望的 IDE 集成类型,但它确实提供 fail-fast 拼写错误检测,因此您 always 第一次 您尝试使用不存在的密钥时会收到通知。另一个好处是,您将获得一个堆栈跟踪,显示带有拼写错误关键字的行号,而不是 nil 值导致 NPE 的行号(可能很远)。

它同样适用于记录和普通旧地图(我常用的用例)。

来自自述文件:


地图值查找

地图很方便,尤其是当关键字用作函数来查找地图中的值时。不幸的是,试图在地图中查找不存在的关键字将 return nil。虽然有时很方便,但这意味着关键字名称中的一个简单拼写错误将无声地 return 损坏数据(即 nil)而不是所需的值。

相反,使用函数 grab 进行 keyword/map 查找:

(grab k m)
  "A fail-fast version of keyword/map lookup.  When invoked as (grab :the-key the-map),
   returns the value associated with :the-key as for (clojure.core/get the-map :the-key).
   Throws an Exception if :the-key is not present in the-map."

(def sidekicks {:batman "robin" :clark "lois"})
(grab :batman sidekicks)
;=> "robin"

(grab :spiderman m)
;=> IllegalArgumentException Key not present in map:
map : {:batman "robin", :clark "lois"}
keys: [:spiderman]

也应该使用函数 grab 代替 clojure。core/get。只需反转参数的顺序即可匹配 "keyword-first, map-second" 约定。

为了在嵌套映射中查找值,fetch-in 函数取代了 clojure。core/get-in:

(fetch-in m ks)
  "A fail-fast version of clojure.core/get-in. When invoked as (fetch-in the-map keys-vec),
   returns the value associated with keys-vec as for (clojure.core/get-in the-map keys-vec).
   Throws an Exception if the path keys-vec is not present in the-map."

(def my-map {:a 1 
             :b {:c 3}})
(fetch-in my-map [:b :c])
3
(fetch-in my-map [:b :z])
;=> IllegalArgumentException Key seq not present in map:
;=>   map : {:b {:c 3}, :a 1}
;=>   keys: [:b :z]

你的另一个选择,使用记录,是使用访问器的 Java-interop 风格:

(.field-name myrec)

由于 Clojure defrecord 编译成简单的 Java class,您的 IDE 可能 能够识别这些名称更容易。 YMMV

因为 defrecords 编译为 java classes 并且 java class 上的字段没有名称空间的概念,我认为没有让 defrecord 发出命名空间关键字的好方法。

如果代码对性能不敏感并且不需要实现任何协议和类似协议,另一种选择是只使用映射。

另一种方法是像 Alan Thompson 的解决方案一样,实现安全获取功能。 prismatic/plumbing util 库也有这个的实现。

(defn safe-get [m k]
  (let [ret (get m k ::not-found)]
    (if (= ::not-found ret)
      (throw (ex-info "Key not found: " {:map m, :key k}))
      ret)))

(defrecord x [foo])

(safe-get (->x 1) :foo) ;=> 1

(safe-get (->x 1) :fo) ;=>

;; 1. Unhandled clojure.lang.ExceptionInfo
;;  Key not found:
;;  {:map {:foo 1}, :key :fo}