命名空间限定记录字段访问器
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}
同样的愚蠢错误我已经犯过很多次了:
(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}